Added MacOS SDK

This commit is contained in:
Andrew Zambazos
2026-06-11 14:04:52 +12:00
parent ffdc88608e
commit 553ab6537a
2151 changed files with 450464 additions and 0 deletions
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2013, 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.ActivateButtonNavigationItem = class ActivateButtonNavigationItem extends WI.ButtonNavigationItem
{
constructor(identifier, defaultToolTip, activatedToolTip, image, imageWidth, imageHeight, role)
{
super(identifier, defaultToolTip, image, imageWidth, imageHeight, role);
this._defaultToolTip = defaultToolTip;
this._activatedToolTip = activatedToolTip || defaultToolTip;
}
// Public
get defaultToolTip()
{
return this._defaultToolTip;
}
set defaultToolTip(defaultToolTip)
{
this._defaultToolTip = defaultToolTip;
if (!this.activated)
this.tooltip = this._defaultToolTip;
}
get activatedToolTip()
{
return this._activatedToolTip;
}
set activatedToolTip(activatedToolTip)
{
this._activatedToolTip = activatedToolTip;
if (this.activated)
this.tooltip = this._activatedToolTip;
}
get activated()
{
return this.element.classList.contains(WI.ActivateButtonNavigationItem.ActivatedStyleClassName);
}
set activated(flag)
{
flag = !!flag;
this.element.classList.toggle(WI.ActivateButtonNavigationItem.ActivatedStyleClassName, flag);
this.tooltip = flag ? this._activatedToolTip : this._defaultToolTip;
this.element.ariaPressed = flag;
this.element.ariaLabel = this.tooltip;
}
// Protected
get additionalClassNames()
{
return ["activate", "button"];
}
};
WI.ActivateButtonNavigationItem.ActivatedStyleClassName = "activated";
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2013, 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.ActivateButtonToolbarItem = class ActivateButtonToolbarItem extends WI.ActivateButtonNavigationItem
{
constructor(identifier, defaultToolTip, activatedToolTip, image, role)
{
super(identifier, defaultToolTip, activatedToolTip, image, 16, 16, role);
}
};
+62
View File
@@ -0,0 +1,62 @@
/*
* 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.
*/
.alignment-editor .glyph {
display: inline-block;
width: 16px;
height: 16px;
padding: 2px;
background-color: var(--background-color-content);
box-sizing: content-box;
border: 1px solid var(--border-color);
color: var(--glyph-color);
}
.alignment-editor .glyph:not(:first-child) {
margin-inline-start: 2px;
}
.alignment-editor .glyph:active {
color: var(--glyph-color-pressed);
}
.alignment-editor .glyph.selected {
background-color: var(--glyph-color-active);
border-color: var(--glyph-color-active);
color: var(--selected-foreground-color);
}
.alignment-editor .glyph.selected:active {
background-color: var(--glyph-color-active-pressed);
border-color: var(--glyph-color-active-pressed);
}
.alignment-editor .glyph.rotate-left > svg {
rotate: -90deg;
}
.alignment-editor .glyph:focus {
outline-offset: var(--focus-ring-outline-offset);
}
+161
View File
@@ -0,0 +1,161 @@
/*
* 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.AlignmentEditor = class AlignmentEditor extends WI.Object
{
constructor()
{
super();
this._alignment = null;
this._valueToGlyphElement = new Map;
this._element = document.createElement("div");
this._element.className = "alignment-editor";
this._element.role = "radiogroup";
}
// Static
static glyphPath(alignment)
{
let glyphs = WI.AlignmentEditor._glyphsForType(alignment.type);
console.assert(glyphs, `No glyphs found for propertyName: ${alignment.type}`);
return glyphs?.[alignment.text] || WI.AlignmentEditor.UnknownValueGlyph;
}
static shouldRotateGlyph(type)
{
// FIXME: <https://webkit.org/b/233053> Web Inspector: mirror/rotate alignment icons when flex-direction/grid-auto-flow/RTL affect axis or direction
switch (type) {
case WI.AlignmentData.Type.JustifyContent:
case WI.AlignmentData.Type.JustifyItems:
case WI.AlignmentData.Type.JustifySelf:
return true;
case WI.AlignmentData.Type.AlignContent:
case WI.AlignmentData.Type.AlignItems:
case WI.AlignmentData.Type.AlignSelf:
return false;
}
console.assert(false, "Unsupported type", type);
return false;
}
static _glyphsForType(type)
{
switch (type) {
case WI.AlignmentData.Type.AlignContent:
case WI.AlignmentData.Type.JustifyContent:
return WI.AlignmentEditor.AlignContentGlyphs;
case WI.AlignmentData.Type.AlignItems:
case WI.AlignmentData.Type.AlignSelf:
case WI.AlignmentData.Type.JustifyItems:
case WI.AlignmentData.Type.JustifySelf:
return WI.AlignmentEditor.AlignItemsGlyphs;
}
return null;
}
// Public
get element() { return this._element; }
get alignment()
{
return this._alignment;
}
set alignment(alignment)
{
console.assert(alignment instanceof WI.AlignmentData);
if (this._alignment?.type !== alignment.type) {
this._valueToGlyphElement.clear();
this._element.removeChildren();
// FIXME: <https://webkit.org/b/233053> Web Inspector: mirror/rotate alignment icons when flex-direction/grid-auto-flow/RTL affect axis or direction
let shouldRotate = WI.AlignmentEditor.shouldRotateGlyph(alignment.type)
for (let [value, path] of Object.entries(WI.AlignmentEditor._glyphsForType(alignment.type))) {
let glyphElement = WI.ImageUtilities.useSVGSymbol(path, "glyph", value);
glyphElement.role = "radio";
glyphElement.tabIndex = 0;
this._element.append(glyphElement);
glyphElement.classList.toggle("rotate-left", shouldRotate);
glyphElement.addEventListener("click", () => {
this._removePreviouslySelected();
this._alignment.text = value;
this._updateSelected();
this.dispatchEventToListeners(WI.AlignmentEditor.Event.ValueChanged, {alignment: this._alignment});
});
this._valueToGlyphElement.set(value, glyphElement);
}
} else
this._removePreviouslySelected();
this._alignment = alignment;
this._updateSelected();
}
// Private
_removePreviouslySelected()
{
let previousGlyphElement = this._valueToGlyphElement.get(this._alignment.text);
previousGlyphElement?.classList.remove("selected");
previousGlyphElement?.removeAttribute("aria-checked");
}
_updateSelected()
{
let glyphElement = this._valueToGlyphElement.get(this._alignment.text);
glyphElement?.classList.add("selected");
glyphElement?.setAttribute("aria-checked", true);
}
};
// FIXME: <https://webkit.org/b/233055> Web Inspector: Add a swatch for justify-content, justify-items, and justify-self
WI.AlignmentEditor.AlignContentGlyphs = {
"start": "Images/AlignContentStart.svg",
"center": "Images/AlignContentCenter.svg",
"end": "Images/AlignContentEnd.svg",
"space-between": "Images/AlignContentSpaceBetween.svg",
"space-around": "Images/AlignContentSpaceAround.svg",
"space-evenly": "Images/AlignContentSpaceEvenly.svg",
"stretch": "Images/AlignContentStretch.svg",
};
WI.AlignmentEditor.AlignItemsGlyphs = {
"start": "Images/AlignItemsStart.svg",
"center": "Images/AlignItemsCenter.svg",
"end": "Images/AlignItemsEnd.svg",
"stretch": "Images/AlignItemsStretch.svg",
};
WI.AlignmentEditor.UnknownValueGlyph = "Images/AlignmentUnknown.svg";
WI.AlignmentEditor.Event = {
ValueChanged: "alignment-editor-value-changed",
};
@@ -0,0 +1,29 @@
/*
* 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.
*/
.content-view.animation-collection {
position: relative;
padding: 4px;
}
@@ -0,0 +1,110 @@
/*
* 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.AnimationCollectionContentView = class AnimationCollectionContentView extends WI.CollectionContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.AnimationCollection);
let contentPlaceholder = document.createElement("div");
let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
descriptionElement.className = "description";
switch (representedObject.animationType) {
case WI.Animation.Type.WebAnimation:
descriptionElement.textContent = WI.UIString("Waiting for animations created by JavaScript.");
break;
case WI.Animation.Type.CSSAnimation:
descriptionElement.textContent = WI.UIString("Waiting for animations created by CSS.");
break;
case WI.Animation.Type.CSSTransition:
descriptionElement.textContent = WI.UIString("Waiting for transitions created by CSS.");
break;
}
console.assert(descriptionElement.textContent);
super(representedObject, WI.AnimationContentView, contentPlaceholder);
this.selectionEnabled = true;
this.element.classList.add("animation-collection");
}
// Public
handleRefreshButtonClicked()
{
for (let subview of this.subviews) {
if (subview instanceof WI.AnimationContentView)
subview.handleRefreshButtonClicked();
}
}
// Protected
contentViewAdded(contentView)
{
contentView.element.addEventListener("mouseenter", this._handleContentViewMouseEnter);
contentView.element.addEventListener("mouseleave", this._handleContentViewMouseLeave);
}
contentViewRemoved(contentView)
{
contentView.element.removeEventListener("mouseenter", this._handleContentViewMouseEnter);
contentView.element.removeEventListener("mouseleave", this._handleContentViewMouseLeave);
}
detached()
{
WI.domManager.hideDOMNodeHighlight();
super.detached();
}
// Private
_handleContentViewMouseEnter(event)
{
let contentView = WI.View.fromElement(event.target);
if (!(contentView instanceof WI.AnimationContentView))
return;
let animation = contentView.representedObject;
animation.requestEffectTarget((styleable) => {
if (!styleable.node || !styleable.node.ownerDocument)
return;
styleable.node.highlight();
});
}
_handleContentViewMouseLeave(event)
{
WI.domManager.hideDOMNodeHighlight();
}
};
+135
View File
@@ -0,0 +1,135 @@
/*
* 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.
*/
.content-view.animation {
position: relative;
width: calc(100% - 8px);
margin: 4px;
background-color: var(--background-color-content);
border: 1px solid var(--border-color);
}
.content-view.animation.selected {
outline: auto -webkit-focus-ring-color;
}
.content-view.animation > header {
display: flex;
align-items: center;
height: var(--navigation-bar-height);
padding: 0 6px;
font-size: 13px;
}
.content-view.animation > header > .titles {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.content-view.animation > header > .titles > .title {
color: var(--text-color-gray-dark);
}
.content-view.animation > header > .titles > .title > code {
font-family: Menlo, monospace;
font-size: 11px;
}
.content-view.animation > header > .titles > .subtitle {
color: var(--text-color-gray-medium);
}
.content-view.animation > header > .titles > .subtitle:not(:empty)::before {
content: "\00A0\2014\00A0"; /* &nbsp;&mdash;&nbsp; */;
}
.content-view.animation > header > .navigation-bar {
border: none;
opacity: 0;
}
.content-view.animation:hover > header > .navigation-bar {
opacity: 1;
}
.content-view.animation > .preview {
display: flex;
justify-content: center;
align-items: center;
background-color: hsl(0, 0%, 96%);
}
.content-view.animation > .preview > svg {
width: 100%;
}
body[dir=rtl] .content-view.animation > .preview > svg {
transform: scaleX(-1);
}
.content-view.animation > .preview > svg rect {
fill: transparent;
}
.content-view.animation > .preview > svg > .delay line {
stroke: var(--border-color);
stroke-width: 2;
}
.content-view.animation > .preview > svg > .active path {
fill: var(--glyph-color-active);
fill-opacity: 0.5;
stroke: var(--glyph-color-active);
stroke-width: 1;
}
.content-view.animation > .preview > svg > .active circle {
fill: var(--text-color);
}
.content-view.animation > .preview > svg > .active line {
stroke: var(--text-color);
stroke-width: 2;
}
.content-view.animation > .preview > span {
padding: 4px;
color: var(--text-color-secondary);
}
@media (prefers-color-scheme: dark) {
.content-view.animation > header > .titles > .title {
color: var(--text-color);
}
.content-view.animation > header > .titles > .subtitle {
color: var(--text-color-secondary);
}
.content-view.animation > .preview {
background-color: hsl(0, 0%, 20%);
}
}
+409
View File
@@ -0,0 +1,409 @@
/*
* 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.AnimationContentView = class AnimationContentView extends WI.ContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.Animation);
super(representedObject);
this._animationTargetDOMNode = null;
this._cachedWidth = NaN;
this.element.classList.add("animation");
}
// Static
static get previewHeight()
{
return 40;
}
// Public
handleRefreshButtonClicked()
{
this._refreshSubtitle();
}
// Protected
initialLayout()
{
super.initialLayout();
let headerElement = this.element.appendChild(document.createElement("header"));
let titlesContainer = headerElement.appendChild(document.createElement("div"));
titlesContainer.className = "titles";
this._titleElement = titlesContainer.appendChild(document.createElement("span"));
this._titleElement.className = "title";
this._subtitleElement = titlesContainer.appendChild(document.createElement("span"));
this._subtitleElement.className = "subtitle";
let navigationBar = new WI.NavigationBar;
let animationTargetButtonNavigationItem = new WI.ButtonNavigationItem("animation-target", WI.UIString("Animation Target"), "Images/Markup.svg", 16, 16);
animationTargetButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
WI.addMouseDownContextMenuHandlers(animationTargetButtonNavigationItem.element, this._populateAnimationTargetButtonContextMenu.bind(this));
navigationBar.addNavigationItem(animationTargetButtonNavigationItem);
headerElement.append(navigationBar.element);
this.addSubview(navigationBar);
this._previewContainer = this.element.appendChild(document.createElement("div"));
this._previewContainer.className = "preview";
}
layout()
{
super.layout();
this._refreshTitle();
this._refreshSubtitle();
this._refreshPreview();
}
sizeDidChange()
{
super.sizeDidChange();
this._cachedWidth = this.element.realOffsetWidth;
}
attached()
{
super.attached();
this.representedObject.addEventListener(WI.Animation.Event.NameChanged, this._handleNameChanged, this);
this.representedObject.addEventListener(WI.Animation.Event.EffectChanged, this._handleEffectChanged, this);
this.representedObject.addEventListener(WI.Animation.Event.TargetChanged, this._handleTargetChanged, this);
}
detached()
{
this.representedObject.removeEventListener(WI.Animation.Event.TargetChanged, this._handleTargetChanged, this);
this.representedObject.removeEventListener(WI.Animation.Event.EffectChanged, this._handleEffectChanged, this);
this.representedObject.removeEventListener(WI.Animation.Event.NameChanged, this._handleNameChanged, this);
super.detached();
}
// Private
_refreshTitle()
{
this._titleElement.removeChildren();
let displayName = this.representedObject.displayName;
let showIdentifierIfDifferent = (cssName) => {
let formatString = "";
let substitutions = [];
if (cssName === displayName)
formatString = WI.UIString("(%s)");
else {
formatString = WI.UIString("%s (%s)");
substitutions.push(displayName);
}
let cssNameWrapper = document.createElement("code");
cssNameWrapper.textContent = cssName;
substitutions.push(cssNameWrapper);
String.format(formatString, substitutions, String.standardFormatters, this._titleElement, function(a, b) {
a.append(b);
return a;
});
};
switch (this.representedObject.animationType) {
case WI.Animation.Type.WebAnimation:
this._titleElement.textContent = this.representedObject.name || WI.UIString("(%s)").format(displayName);
break;
case WI.Animation.Type.CSSAnimation:
showIdentifierIfDifferent(this.representedObject.cssAnimationName);
break;
case WI.Animation.Type.CSSTransition:
showIdentifierIfDifferent(this.representedObject.cssTransitionProperty);
break;
}
}
_refreshSubtitle()
{
this.representedObject.requestEffectTarget((styleable) => {
this._animationTargetDOMNode = styleable.node;
this._subtitleElement.removeChildren();
if (styleable)
this._subtitleElement.appendChild(WI.linkifyStyleable(styleable));
});
}
_refreshPreview()
{
this._previewContainer.removeChildren();
let keyframes = this.representedObject.keyframes;
if (!keyframes.length) {
let span = this._previewContainer.appendChild(document.createElement("span"));
span.textContent = WI.UIString("This animation has no keyframes.");
return;
}
let startDelay = this.representedObject.startDelay || 0;
let iterationDuration = this.representedObject.iterationDuration || 0;
let endDelay = this.representedObject.endDelay || 0;
let totalDuration = startDelay + iterationDuration + endDelay;
if (totalDuration === 0) {
let span = this._previewContainer.appendChild(document.createElement("span"));
span.textContent = WI.UIString("This animation has no duration.");
return;
}
const previewHeight = WI.AnimationContentView.previewHeight;
const markerHeadRadius = 4;
const markerHeadPadding = 2;
// Squeeze the entire preview so that markers aren't cut off.
const squeezeXStart = (iterationDuration && startDelay) ? 0 : markerHeadRadius + markerHeadPadding;
const squeezeXEnd = (iterationDuration && endDelay) ? 0 : markerHeadRadius + markerHeadPadding;
const squeezeYStart = markerHeadRadius + (markerHeadPadding * 2);
// Ensure that at least a line is drawn if the output of the timing function is `0`.
const adjustEasingHeight = -1;
// Move the easing line down to cut off the bottom border.
const adjustEasingBottomY = 0.5;
let secondsPerPixel = this._cachedWidth / totalDuration;
startDelay *= secondsPerPixel;
iterationDuration = (iterationDuration * secondsPerPixel) - squeezeXStart - squeezeXEnd;
endDelay *= secondsPerPixel;
let svg = this._previewContainer.appendChild(createSVGElement("svg"));
svg.setAttribute("viewBox", `0 0 ${this._cachedWidth} ${previewHeight}`);
function addTitle(parent, title) {
let titleElement = parent.appendChild(createSVGElement("title"));
titleElement.textContent = title;
}
if (startDelay) {
let startDelayContainer = svg.appendChild(createSVGElement("g"));
startDelayContainer.classList.add("delay", "start");
let startDelayLine = startDelayContainer.appendChild(createSVGElement("line"));
startDelayLine.setAttribute("y1", (previewHeight + squeezeYStart) / 2);
startDelayLine.setAttribute("x2", startDelay);
startDelayLine.setAttribute("y2", (previewHeight + squeezeYStart) / 2);
let startDelayElement = startDelayContainer.appendChild(createSVGElement("rect"));
startDelayElement.setAttribute("width", startDelay);
startDelayElement.setAttribute("height", previewHeight);
const startDelayTitleFormat = WI.UIString("Start Delay %s", "Web Animation Start Delay Tooltip", "Tooltip for section of graph representing delay before a web animation begins applying styles");
addTitle(startDelayElement, startDelayTitleFormat.format(Number.secondsToString(this.representedObject.startDelay / 1000)));
}
if (endDelay) {
let endDelayContainer = svg.appendChild(createSVGElement("g"));
endDelayContainer.setAttribute("transform", `translate(${startDelay + iterationDuration + squeezeXStart}, 0)`);
endDelayContainer.classList.add("delay", "end");
let endDelayLine = endDelayContainer.appendChild(createSVGElement("line"));
endDelayLine.setAttribute("y1", (previewHeight + squeezeYStart) / 2);
endDelayLine.setAttribute("x2", endDelay);
endDelayLine.setAttribute("y2", (previewHeight + squeezeYStart) / 2);
let endDelayElement = endDelayContainer.appendChild(createSVGElement("rect"));
endDelayElement.setAttribute("width", startDelay + iterationDuration + endDelay);
endDelayElement.setAttribute("height", previewHeight);
const endDelayTitleFormat = WI.UIString("End Delay %s", "Web Animation End Delay Tooltip", "Tooltip for section of graph representing delay after a web animation finishes applying styles");
addTitle(endDelayElement, endDelayTitleFormat.format(Number.secondsToString(this.representedObject.endDelay / 1000)));
}
if (iterationDuration) {
let timingFunction = this.representedObject.timingFunction;
let activeDurationContainer = svg.appendChild(createSVGElement("g"));
activeDurationContainer.classList.add("active");
activeDurationContainer.setAttribute("transform", `translate(${startDelay + squeezeXStart}, ${squeezeYStart})`);
const startY = 0;
const endY = previewHeight - squeezeYStart;
const height = endY - startY;
for (let [keyframeA, keyframeB] of keyframes.adjacencies()) {
let startX = iterationDuration * keyframeA.offset;
let endX = iterationDuration * keyframeB.offset;
let width = endX - startX;
let easing = keyframeA.easing || timingFunction;
let easingContainer = activeDurationContainer.appendChild(createSVGElement("g"));
easingContainer.classList.add("easing");
easingContainer.setAttribute("transform", `translate(${startX}, ${startY})`);
let easingPath = easingContainer.appendChild(createSVGElement("path"));
let pathSteps = [];
if (easing instanceof WI.CubicBezier) {
pathSteps.push("C");
pathSteps.push(easing.inPoint.x * width); // x1
pathSteps.push((1 - easing.inPoint.y) * (height + adjustEasingHeight)); // y1
pathSteps.push(easing.outPoint.x * width); // x2
pathSteps.push((1 - easing.outPoint.y) * (height + adjustEasingHeight)); // y2
pathSteps.push(width); // x
pathSteps.push(0); // y
} else if (easing instanceof WI.StepsFunction) {
let goUpFirst = false;
let stepStartAdjust = 0;
let stepCountAdjust = 0;
switch (easing.type) {
case WI.StepsFunction.Type.JumpStart:
case WI.StepsFunction.Type.Start:
goUpFirst = true;
break;
case WI.StepsFunction.Type.JumpNone:
--stepCountAdjust;
break;
case WI.StepsFunction.Type.JumpBoth:
++stepStartAdjust;
++stepCountAdjust;
break;
}
let stepCount = easing.count + stepCountAdjust;
let stepX = width / easing.count; // Always use the defined number of steps to divide the duration.
let stepY = (height + adjustEasingHeight) / stepCount;
for (let i = stepStartAdjust; i <= stepCount; ++i) {
let x = stepX * (i - stepStartAdjust);
let y = height + adjustEasingHeight - (stepY * i);
if (goUpFirst) {
if (i)
pathSteps.push("H", x);
if (i < stepCount)
pathSteps.push("V", y - stepY);
} else {
pathSteps.push("V", y);
if (i < stepCount || stepCountAdjust < 0)
pathSteps.push("H", x + stepX);
}
}
} else if (easing instanceof WI.Spring) {
let duration = easing.calculateDuration();
for (let i = 0; i < width; i += 1 / window.devicePixelRatio)
pathSteps.push("L", i, easing.solve(duration * i / width) * (height + adjustEasingHeight));
}
easingPath.setAttribute("d", `M 0 ${height + adjustEasingBottomY} ${pathSteps.join(" ")} V ${height + adjustEasingBottomY} Z`);
let titleRect = easingContainer.appendChild(createSVGElement("rect"));
titleRect.setAttribute("width", width);
titleRect.setAttribute("height", height);
addTitle(titleRect, easing.toString());
}
for (let keyframe of keyframes) {
let x = iterationDuration * keyframe.offset;
let keyframeContainer = activeDurationContainer.appendChild(createSVGElement("g"));
keyframeContainer.classList.add("keyframe");
keyframeContainer.setAttribute("transform", `translate(${x}, ${startY})`);
let keyframeMarkerHead = keyframeContainer.appendChild(createSVGElement("circle"));
keyframeMarkerHead.setAttribute("r", markerHeadRadius);
let keyframeMarkerLine = keyframeContainer.appendChild(createSVGElement("line"));
keyframeMarkerLine.setAttribute("y1", height);
let titleRect = keyframeContainer.appendChild(createSVGElement("rect"));
titleRect.setAttribute("x", -1 * (markerHeadRadius + markerHeadPadding));
titleRect.setAttribute("y", -1 * squeezeYStart);
titleRect.setAttribute("width", (markerHeadRadius + markerHeadPadding) * 2);
titleRect.setAttribute("height", height + squeezeYStart);
addTitle(titleRect, keyframe.style);
}
}
}
_handleNameChanged(event)
{
if (!this.didInitialLayout)
return;
this._refreshTitle();
}
_handleEffectChanged(event)
{
this._refreshPreview();
}
_handleTargetChanged(event)
{
this._refreshSubtitle();
}
_populateAnimationTargetButtonContextMenu(contextMenu)
{
contextMenu.appendItem(WI.UIString("Log Animation"), () => {
WI.RemoteObject.resolveAnimation(this.representedObject, WI.RuntimeManager.ConsoleObjectGroup, (remoteObject) => {
if (!remoteObject)
return;
const text = WI.UIString("Selected Animation", "Appears as a label when a given web animation is logged to the Console");
WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, {addSpecialUserLogClass: true});
});
});
contextMenu.appendSeparator();
if (this._animationTargetDOMNode)
WI.appendContextMenuItemsForDOMNode(contextMenu, this._animationTargetDOMNode);
}
};
@@ -0,0 +1,42 @@
/*
* 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.
*/
.sidebar > .panel.details.animation > .content > .details-section.animation-keyframes .header > .subtitle {
color: var(--text-color-gray-medium);
}
.sidebar > .panel.details.animation > .content > .details-section.animation-keyframes .details-section {
background-color: var(--background-color-intermediate);
}
.sidebar > .panel.details.animation > .content > .details-section.animation-keyframes .details-section .row.styles {
display: table-row;
}
.sidebar > .panel.details.animation > .content > .details-section.animation-keyframes .details-section .row.styles .CodeMirror {
height: auto;
padding: 4px 0;
background-color: transparent;
}
@@ -0,0 +1,295 @@
/*
* 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.AnimationDetailsSidebarPanel = class AnimationDetailsSidebarPanel extends WI.DetailsSidebarPanel
{
constructor()
{
super("animation", WI.UIString("Animation"));
this._animation = null;
this._codeMirrorSectionMap = new Map;
}
// Public
inspect(objects)
{
if (!(objects instanceof Array))
objects = [objects];
this.animation = objects.find((object) => object instanceof WI.Animation);
return !!this.animation;
}
get animation()
{
return this._animation;
}
set animation(animation)
{
if (animation === this._animation)
return;
if (this._animation) {
this._animation.removeEventListener(WI.Animation.Event.TargetChanged, this._handleAnimationTargetChanged, this);
this._animation.removeEventListener(WI.Animation.Event.EffectChanged, this._handleAnimationEffectChanged, this);
this._animation.removeEventListener(WI.Animation.Event.NameChanged, this._handleAnimationNameChanged, this);
}
this._animation = animation || null;
if (this._animation) {
this._animation.addEventListener(WI.Animation.Event.NameChanged, this._handleAnimationNameChanged, this);
this._animation.addEventListener(WI.Animation.Event.EffectChanged, this._handleAnimationEffectChanged, this);
this._animation.addEventListener(WI.Animation.Event.TargetChanged, this._handleAnimationTargetChanged, this);
}
this.needsLayout();
}
// Protected
initialLayout()
{
super.initialLayout();
this._idRow = new WI.DetailsSectionSimpleRow(WI.UIString("Identifier"));
this._typeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Type"));
this._cssAnimationNameRow = new WI.DetailsSectionSimpleRow(WI.UIString("Name"));
this._cssTransitionPropertyRow = new WI.DetailsSectionSimpleRow(WI.UIString("Property"));
this._targetRow = new WI.DetailsSectionSimpleRow(WI.UIString("Target", "Web Animation Target Label", "Label for the current DOM node target of a web animation"));
const identitySectionTitle = WI.UIString("Identity", "Web Animation Identity Title", "Section title for information about a web animation");
let identitySection = new WI.DetailsSection("animation-identity", identitySectionTitle, [new WI.DetailsSectionGroup([this._idRow, this._typeRow, this._cssAnimationNameRow, this._cssTransitionPropertyRow, this._targetRow])]);
this.contentView.element.appendChild(identitySection.element);
this._iterationCountRow = new WI.DetailsSectionSimpleRow(WI.UIString("Iterations", "Web Animation Iteration Count Label", "Label for the number of iterations of a web animation"));
this._iterationStartRow = new WI.DetailsSectionSimpleRow(WI.UIString("Start", "Web Animation Iteration Start Label", "Label for the number describing which iteration a web animation should start at"));
this._iterationDurationRow = new WI.DetailsSectionSimpleRow(WI.UIString("Duration", "Web Animation Iteration Duration Label", "Label for the time duration of each iteration of a web animation"));
let iterationsGroup = new WI.DetailsSectionGroup([this._iterationCountRow, this._iterationStartRow, this._iterationDurationRow]);
this._startDelayRow = new WI.DetailsSectionSimpleRow(WI.UIString("Start Delay", "Web Animation Start Delay Label", "Label for the start delay time of a web animation "));
this._endDelayRow = new WI.DetailsSectionSimpleRow(WI.UIString("End Delay", "Web Animation End Delay Label", "Label for the end delay time of a web animation "));
this._timingFunctionRow = new WI.DetailsSectionSimpleRow(WI.UIString("Easing", "Web Animation Easing Label", "Label for the cubic-bezier timing function of a web animation"));
let timingGroup = new WI.DetailsSectionGroup([this._startDelayRow, this._endDelayRow, this._timingFunctionRow]);
this._playbackDirectionRow = new WI.DetailsSectionSimpleRow(WI.UIString("Direction", "Web Animation Playback Direction Label", "Label for the playback direction of a web animation"));
this._fillModeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Fill", "Web Animation Fill Mode Label", "Label for the fill mode of a web animation"));
let fillDirectionGroup = new WI.DetailsSectionGroup([this._playbackDirectionRow, this._fillModeRow]);
const effectSectionTitle = WI.UIString("Effect", "Web Animation Effect Title", "Section title for information about the effect of a web animation");
let effectSection = new WI.DetailsSection("animation-effect", effectSectionTitle, [iterationsGroup, timingGroup, fillDirectionGroup]);
this.contentView.element.appendChild(effectSection.element);
this._keyframesGroup = new WI.DetailsSectionGroup;
const keyframesSectionTitle = WI.UIString("Keyframes", "Web Animation Keyframes Title", "Section title for information about the keyframes of a web animation");
let keyframesSection = new WI.DetailsSection("animation-keyframes", keyframesSectionTitle, [this._keyframesGroup]);
this.contentView.element.appendChild(keyframesSection.element);
const selectable = false;
let backtraceTreeOutline = new WI.TreeOutline(selectable);
backtraceTreeOutline.disclosureButtons = false;
this._backtraceTreeController = new WI.StackTraceTreeController(backtraceTreeOutline);
let backtraceRow = new WI.DetailsSectionRow;
backtraceRow.element.appendChild(backtraceTreeOutline.element);
const backtraceSectionTitle = WI.UIString("Backtrace", "Web Animation Backtrace Title", "Section title for the JavaScript backtrace of the creation of a web animation");
this._backtraceSection = new WI.DetailsSection("animation-backtrace", backtraceSectionTitle, [new WI.DetailsSectionGroup([backtraceRow])]);
this._backtraceSection.element.hidden = true;
this.contentView.element.appendChild(this._backtraceSection.element);
}
layout()
{
super.layout();
if (!this._animation)
return;
this._refreshIdentitySection();
this._refreshEffectSection();
this._refreshBacktraceSection();
for (let codeMirror of this._codeMirrorSectionMap.values())
codeMirror.refresh();
}
attached()
{
super.attached();
for (let codeMirror of this._codeMirrorSectionMap.values())
codeMirror.refresh();
}
// Private
_refreshIdentitySection()
{
let animationType = this._animation.animationType;
let displayName = this._animation.displayName;
let cssAnimationName = this._animation.cssAnimationName;
let cssTransitionProperty = this._animation.cssTransitionProperty;
switch (animationType) {
case WI.Animation.Type.WebAnimation:
this._idRow.value = this._animation.name;
break;
case WI.Animation.Type.CSSAnimation:
this._idRow.value = cssAnimationName !== displayName ? displayName : null;
break;
case WI.Animation.Type.CSSTransition:
this._idRow.value = cssTransitionProperty !== displayName ? displayName : null;
break;
}
this._typeRow.value = WI.Animation.displayNameForAnimationType(animationType);
this._cssAnimationNameRow.value = cssAnimationName;
this._cssTransitionPropertyRow.value = cssTransitionProperty;
this._targetRow.value = null;
this._animation.requestEffectTarget((styleable) => {
this._targetRow.value = WI.linkifyStyleable(styleable);
});
}
_refreshEffectSection()
{
for (let section of this._codeMirrorSectionMap.keys())
section.removeEventListener(WI.DetailsSection.Event.CollapsedStateChanged, this._handleDetailsSectionCollapsedStateChanged, this);
this._codeMirrorSectionMap.clear();
const precision = 0;
this._iterationCountRow.value = !isNaN(this._animation.iterationCount) ? this._animation.iterationCount.toLocaleString() : null;
this._iterationStartRow.value = !isNaN(this._animation.iterationStart) ? this._animation.iterationStart.toLocaleString() : null;
this._iterationDurationRow.value = !isNaN(this._animation.iterationDuration) ? Number.secondsToString(this._animation.iterationDuration / 1000) : null;
this._startDelayRow.value = this._animation.startDelay ? Number.secondsToString(this._animation.startDelay / 1000) : null;
this._endDelayRow.value = this._animation.endDelay ? Number.secondsToString(this._animation.endDelay / 1000) : null;
this._timingFunctionRow.value = this._animation.timingFunction ? this._animation.timingFunction.toString() : null;
this._playbackDirectionRow.value = this._animation.playbackDirection ? WI.Animation.displayNameForPlaybackDirection(this._animation.playbackDirection) : null;
this._fillModeRow.value = this._animation.fillMode ? WI.Animation.displayNameForFillMode(this._animation.fillMode) : null;
let keyframeSections = [];
for (let keyframe of this._animation.keyframes) {
let rows = [];
let keyframeSection = new WI.DetailsSection("animation-keyframe-offset-" + keyframe.offset, Number.percentageString(keyframe.offset, precision));
keyframeSection.addEventListener(WI.DetailsSection.Event.CollapsedStateChanged, this._handleDetailsSectionCollapsedStateChanged, this);
keyframeSections.push(keyframeSection);
if (keyframe.easing) {
let subtitle = keyframeSection.headerElement.appendChild(document.createElement("span"));
subtitle.className = "subtitle";
subtitle.textContent = ` ${emDash} ${keyframe.easing.toString()}`;
}
if (keyframe.style) {
let codeMirrorElement = document.createElement("div");
let codeMirror = WI.CodeMirrorEditor.create(codeMirrorElement, {
mode: "css",
readOnly: "nocursor",
lineWrapping: true,
});
codeMirror.setValue(keyframe.style);
const range = null;
function optionsForType(type) {
return {
allowedTokens: /\btag\b/,
callback(marker, valueObject, valueString) {
let swatch = new WI.InlineSwatch(type, valueObject, {readOnly: true});
codeMirror.setUniqueBookmark(marker.range.startPosition().toCodeMirror(), swatch.element);
}
};
}
createCodeMirrorColorTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Color));
createCodeMirrorGradientTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Gradient));
createCodeMirrorCubicBezierTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Bezier));
createCodeMirrorSpringTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Spring));
let row = new WI.DetailsSectionRow;
row.element.classList.add("styles");
row.element.appendChild(codeMirrorElement);
rows.push(row);
this._codeMirrorSectionMap.set(keyframeSection, codeMirror);
}
if (!rows.length) {
let emptyRow = new WI.DetailsSectionRow(WI.UIString("No Styles"));
emptyRow.showEmptyMessage();
rows.push(emptyRow);
}
keyframeSection.groups = [new WI.DetailsSectionGroup(rows)];
}
if (!keyframeSections.length) {
let emptyRow = new WI.DetailsSectionRow(WI.UIString("No Keyframes"));
emptyRow.showEmptyMessage();
keyframeSections.push(emptyRow);
}
this._keyframesGroup.rows = keyframeSections;
}
_refreshBacktraceSection()
{
let stackTrace = this._animation.stackTrace;
this._backtraceTreeController.stackTrace = stackTrace;
this._backtraceSection.element.hidden = !stackTrace?.callFrames.length;
}
_handleAnimationNameChanged(event)
{
this._refreshIdentitySection();
}
_handleAnimationEffectChanged(event)
{
this._refreshEffectSection();
}
_handleAnimationTargetChanged(event)
{
this._refreshIdentitySection();
}
_handleDetailsSectionCollapsedStateChanged(event)
{
let codeMirror = this._codeMirrorSectionMap.get(event.target);
codeMirror.refresh();
}
};
@@ -0,0 +1,165 @@
/*
* Copyright (C) 2013, 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.ApplicationCacheDetailsSidebarPanel = class ApplicationCacheDetailsSidebarPanel extends WI.DetailsSidebarPanel
{
constructor()
{
super("application-cache-details", WI.UIString("Storage"));
this.element.classList.add("application-cache");
this._applicationCacheFrame = null;
}
// Public
inspect(objects)
{
// Convert to a single item array if needed.
if (!(objects instanceof Array))
objects = [objects];
var applicationCacheFrameToInspect = null;
// Iterate over the objects to find a WI.ApplicationCacheFrame to inspect.
for (var i = 0; i < objects.length; ++i) {
if (objects[i] instanceof WI.ApplicationCacheFrame) {
applicationCacheFrameToInspect = objects[i];
break;
}
}
this.applicationCacheFrame = applicationCacheFrameToInspect;
return !!this.applicationCacheFrame;
}
get applicationCacheFrame()
{
return this._applicationCacheFrame;
}
set applicationCacheFrame(applicationCacheFrame)
{
if (this._applicationCacheFrame === applicationCacheFrame)
return;
this._applicationCacheFrame = applicationCacheFrame;
this.needsLayout();
}
closed()
{
if (this.didInitialLayout) {
WI.applicationCacheManager.removeEventListener(WI.ApplicationCacheManager.Event.NetworkStateUpdated, this._networkStateUpdated, this);
WI.applicationCacheManager.removeEventListener(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, this._frameManifestStatusChanged, this);
}
super.closed();
}
// Protected
initialLayout()
{
super.initialLayout();
this._locationManifestURLRow = new WI.DetailsSectionSimpleRow(WI.UIString("Manifest URL"));
this._locationFrameURLRow = new WI.DetailsSectionSimpleRow(WI.UIString("Frame URL"));
let locationGroup = new WI.DetailsSectionGroup([this._locationManifestURLRow, this._locationFrameURLRow]);
let locationSection = new WI.DetailsSection("application-cache-location", WI.UIString("Location"), [locationGroup]);
this._onlineRow = new WI.DetailsSectionSimpleRow(WI.UIString("Online"));
this._statusRow = new WI.DetailsSectionSimpleRow(WI.UIString("Status"));
let statusGroup = new WI.DetailsSectionGroup([this._onlineRow, this._statusRow]);
let statusSection = new WI.DetailsSection("application-cache-status", WI.UIString("Status"), [statusGroup]);
this.contentView.element.appendChild(locationSection.element);
this.contentView.element.appendChild(statusSection.element);
WI.applicationCacheManager.addEventListener(WI.ApplicationCacheManager.Event.NetworkStateUpdated, this._networkStateUpdated, this);
WI.applicationCacheManager.addEventListener(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, this._frameManifestStatusChanged, this);
}
layout()
{
super.layout();
if (!this.applicationCacheFrame)
return;
this._locationFrameURLRow.value = this.applicationCacheFrame.frame.url;
this._locationManifestURLRow.value = this.applicationCacheFrame.manifest.manifestURL;
this._refreshOnlineRow();
this._refreshStatusRow();
}
// Private
_networkStateUpdated(event)
{
if (!this.applicationCacheFrame)
return;
this._refreshOnlineRow();
}
_frameManifestStatusChanged(event)
{
if (!this.applicationCacheFrame)
return;
console.assert(event.data.frameManifest instanceof WI.ApplicationCacheFrame);
if (event.data.frameManifest !== this.applicationCacheFrame)
return;
this._refreshStatusRow();
}
_refreshOnlineRow()
{
this._onlineRow.value = WI.applicationCacheManager.online ? WI.UIString("Yes") : WI.UIString("No");
}
_refreshStatusRow()
{
this._statusRow.value = WI.ApplicationCacheDetailsSidebarPanel.Status[this.applicationCacheFrame.status];
}
};
// This needs to be kept in sync with ApplicationCacheManager.js.
WI.ApplicationCacheDetailsSidebarPanel.Status = {
0: "Uncached",
1: "Idle",
2: "Checking",
3: "Downloading",
4: "UpdateReady",
5: "Obsolete"
};
@@ -0,0 +1,32 @@
/*
* 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.
*/
.content-view.application-cache-frame > .data-grid {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2010, 2013-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.ApplicationCacheFrameContentView = class ApplicationCacheFrameContentView extends WI.ContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.ApplicationCacheFrame);
super(representedObject);
this.element.classList.add("application-cache-frame");
this._frame = representedObject.frame;
this._emptyView = WI.createMessageTextView(WI.UIString("No Application Cache information available"), false);
this._emptyView.classList.add("hidden");
this.element.appendChild(this._emptyView);
}
attached()
{
super.attached();
WI.applicationCacheManager.addEventListener(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, this._handleFrameManifestStatusChanged, this);
this.updateStatus(this.representedObject.status);
}
detached()
{
WI.applicationCacheManager.removeEventListener(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, this._handleFrameManifestStatusChanged, this);
super.detached();
}
layout()
{
super.layout();
this.updateStatus();
}
saveToCookie(cookie)
{
cookie.type = WI.ContentViewCookieType.ApplicationCache;
cookie.frame = this.representedObject.frame.url;
cookie.manifest = this.representedObject.manifest.manifestURL;
}
get scrollableElements()
{
if (!this._dataGrid)
return [];
return [this._dataGrid.scrollContainer];
}
_handleFrameManifestStatusChanged(event)
{
var frameManifest = event.data.frameManifest;
if (frameManifest !== this.representedObject)
return;
console.assert(frameManifest instanceof WI.ApplicationCacheFrame);
this.updateStatus(frameManifest.status);
}
updateStatus(status)
{
var oldStatus = this._status;
this._status = status;
if (this._status === WI.ApplicationCacheManager.Status.Idle && (oldStatus === WI.ApplicationCacheManager.Status.UpdateReady || !this._resources))
WI.applicationCacheManager.requestApplicationCache(this._frame, this._updateCallback.bind(this));
}
_updateCallback(applicationCache)
{
if (!applicationCache || !applicationCache.manifestURL) {
delete this._manifest;
delete this._creationTime;
delete this._updateTime;
delete this._size;
delete this._resources;
this._emptyView.classList.remove("hidden");
if (this._dataGrid)
this._dataGrid.element.classList.add("hidden");
return;
}
// FIXME: are these variables needed anywhere else?
this._manifest = applicationCache.manifestURL;
this._creationTime = applicationCache.creationTime;
this._updateTime = applicationCache.updateTime;
this._size = applicationCache.size;
this._resources = applicationCache.resources;
if (!this._dataGrid)
this._createDataGrid();
this._populateDataGrid();
this._dataGrid.autoSizeColumns(20, 80);
this._dataGrid.element.classList.remove("hidden");
this._emptyView.classList.add("hidden");
}
_createDataGrid()
{
var columns = {url: {}, type: {}, size: {}};
columns.url.title = WI.UIString("Resource");
columns.url.sortable = true;
columns.type.title = WI.UIString("Type");
columns.type.sortable = true;
columns.size.title = WI.UIString("Size");
columns.size.aligned = "right";
columns.size.sortable = true;
this._dataGrid = new WI.DataGrid(columns);
this._dataGrid.addEventListener(WI.DataGrid.Event.SortChanged, this._sortDataGrid, this);
this._dataGrid.sortColumnIdentifier = "url";
this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Ascending;
this._dataGrid.createSettings("application-cache-frame-content-view");
this.addSubview(this._dataGrid);
this._dataGrid.updateLayout();
}
_sortDataGrid()
{
function numberCompare(columnIdentifier, nodeA, nodeB)
{
return nodeA.data[columnIdentifier] - nodeB.data[columnIdentifier];
}
function localeCompare(columnIdentifier, nodeA, nodeB)
{
return (nodeA.data[columnIdentifier] + "").extendedLocaleCompare(nodeB.data[columnIdentifier] + "");
}
var comparator;
switch (this._dataGrid.sortColumnIdentifier) {
case "type": comparator = localeCompare.bind(this, "type"); break;
case "size": comparator = numberCompare.bind(this, "sizeNumber"); break;
case "url":
default: comparator = localeCompare.bind(this, "url"); break;
}
this._dataGrid.sortNodes(comparator);
}
_populateDataGrid()
{
this._dataGrid.removeChildren();
for (var resource of this._resources) {
var data = {
url: resource.url,
type: resource.type,
size: Number.bytesToString(resource.size),
sizeNumber: resource.size,
};
var node = new WI.DataGridNode(data);
this._dataGrid.appendChild(node);
}
}
_deleteButtonClicked(event)
{
if (!this._dataGrid || !this._dataGrid.selectedNode)
return;
// FIXME: Delete Button semantics are not yet defined. (Delete a single, or all?)
this._deleteCallback(this._dataGrid.selectedNode);
}
_deleteCallback(node)
{
// FIXME: Should we delete a single (selected) resource or all resources?
// InspectorBackend.deleteCachedResource(...)
// this._update();
}
};
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2013, 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.ApplicationCacheFrameTreeElement = class ApplicationCacheFrameTreeElement extends WI.GeneralTreeElement
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.ApplicationCacheFrame);
const title = null;
const subtitle = null;
super("application-cache-frame", title, subtitle, representedObject);
this.updateTitles();
}
updateTitles()
{
var url = this.representedObject.frame.url;
var parsedURL = parseURL(url);
this.mainTitle = WI.displayNameForURL(url, parsedURL);
// Show the host as the subtitle only if it doesn't match the subtitle of the manifest tree element,
// and it doesn't match the mainTitle.
var subtitle = WI.displayNameForHost(parsedURL.host);
var manifestTreeElement = null;
var currentAncestor = this.parent;
while (currentAncestor && !currentAncestor.root) {
if (currentAncestor instanceof WI.ApplicationCacheManifestTreeElement) {
manifestTreeElement = currentAncestor;
break;
}
currentAncestor = currentAncestor.parent;
}
var subtitleIsDuplicate = subtitle === this._mainTitle || (manifestTreeElement && manifestTreeElement.subtitle === subtitle);
this.subtitle = subtitleIsDuplicate ? null : subtitle;
}
};
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2013, 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.ApplicationCacheManifestTreeElement = class ApplicationCacheManifestTreeElement extends WI.StorageTreeElement
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.ApplicationCacheManifest);
super("application-cache-manifest", "", representedObject);
this.hasChildren = true;
this.expanded = true;
}
// Public
get name()
{
if (!this._name)
this._generateTitles();
return this._name;
}
get secondaryName()
{
if (!this._secondaryName)
this._generateTitles();
return this._secondaryName;
}
get categoryName()
{
return WI.UIString("Application Cache");
}
_generateTitles()
{
var parsedURL = parseURL(this.representedObject.manifestURL);
// Prefer the last path component, with a fallback for the host as the main title.
this._name = WI.displayNameForURL(this.representedObject.manifestURL, parsedURL);
// Show the host as the subtitle.
var secondaryName = WI.displayNameForHost(parsedURL.host);
this._secondaryName = this._name !== secondaryName ? secondaryName : null;
}
};
+145
View File
@@ -0,0 +1,145 @@
/*
* 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.
*/
// AreaChart creates a single filled area chart.
//
// Initialize the chart with a size. You can then include a new point
// in the area chart by providing an (x, y) point via `addPoint`. You
// can add point markers (<circle>) with an (x, y) as well.
//
// SVG:
//
// - There is a single path for line.
//
// <div class="area-chart">
// <svg viewBox="0 0 800 75">
// <path d="..."/>
// </svg>
// </div>
WI.AreaChart = class AreaChart extends WI.View
{
constructor()
{
super();
this.element.classList.add("area-chart");
this._chartElement = this.element.appendChild(createSVGElement("svg"));
this._chartElement.setAttribute("preserveAspectRatio", "none");
this._pathElement = this._chartElement.appendChild(createSVGElement("path"));
this._circleElements = [];
this._points = [];
this._markers = [];
this._size = null;
}
// Public
get size()
{
return this._size;
}
set size(size)
{
if (this._size && this._size.equals(size))
return;
this._size = size;
this._chartElement.setAttribute("viewBox", `0 0 ${size.width} ${size.height}`);
this.needsLayout();
}
addPoint(x, y)
{
this._points.push({x, y});
}
clearPoints()
{
this._points = [];
}
addPointMarker(x, y)
{
this._markers.push({x, y});
}
clearPointMarkers()
{
this._markers = [];
}
clear()
{
this.clearPoints();
this.clearPointMarkers();
}
// Protected
layout()
{
super.layout();
if (this.layoutReason === WI.View.LayoutReason.Resize)
return;
if (!this._size)
return;
let pathComponents = [];
pathComponents.push(`M 0 ${this._size.height}`);
for (let point of this._points)
pathComponents.push(`L ${point.x} ${point.y}`);
let lastX = this._points.length ? this._points.lastValue.x : 0;
pathComponents.push(`L ${lastX} ${this._size.height}`);
pathComponents.push("Z");
let pathString = pathComponents.join(" ");
this._pathElement.setAttribute("d", pathString);
if (this._circleElements.length) {
for (let circle of this._circleElements)
circle.remove();
this._circleElements = [];
}
if (this._markers.length) {
for (let {x, y} of this._markers) {
let circle = this._chartElement.appendChild(createSVGElement("circle"));
this._circleElements.push(circle);
circle.setAttribute("cx", x);
circle.setAttribute("cy", y);
}
}
}
};
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2018-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.
*/
.sidebar > .panel.navigation.audit > .content {
display: flex;
flex-direction: column;
top: var(--navigation-bar-height);
}
.sidebar > .panel.navigation.audit > .content > .tree-outline {
flex-grow: 1;
}
.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active {
color: var(--glyph-color-pressed);
}
.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated {
color: var(--glyph-color-active);
}
.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active {
color: var(--glyph-color-active-pressed);
}
.sidebar > .panel.navigation.audit .edit-audits.disabled {
color: var(--glyph-color-disabled);
}
.sidebar > .panel.navigation.audit > .content > .message-text-view {
bottom: var(--navigation-bar-height);
}
.sidebar > .panel.navigation.audit.has-results > .content > .message-text-view.no-enabled-audits {
position: initial;
border-bottom: 1px solid var(--border-color);
}
.content-view.tab.audit .content-view > .audit-version {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: calc(var(--z-index-popover) + 1);
padding: 8px;
font-size: 13px;
text-align: center;
color: var(--text-color-secondary);
}
.content-view.tab.audit .content-view .reference-page-link-container {
position: absolute;
bottom: 6px;
inset-inline-end: 6px;
}
@@ -0,0 +1,407 @@
/*
* 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.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.NavigationSidebarPanel
{
constructor()
{
super("audit", WI.UIString("Audits"));
}
// Static
static _createNavigationItemTitle()
{
return WI.UIString("Create", "Create @ Audit Tab Navigation Sidebar", "Title of button that creates a new audit.");
}
// Public
showDefaultContentView()
{
let contentView = new WI.ContentView;
if (WI.auditManager.editing) {
let contentPlaceholder = WI.createMessageTextView(WI.UIString("Editing audits"));
contentView.element.appendChild(contentPlaceholder);
let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
descriptionElement.className = "description";
descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to edit it.");
let createAuditNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.AuditNavigationSidebarPanel._createNavigationItemTitle(), "Images/Plus15.svg", 15, 15);
createAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
createAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this);
let createAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to create a new audit."), createAuditNavigationItem);
createAuditHelpElement.classList.add("create-audit");
contentPlaceholder.appendChild(createAuditHelpElement);
let stopEditingAuditsNavigationItem = new WI.ButtonNavigationItem("stop-editing-audits", WI.UIString("Done"), "Images/Pencil.svg", 16, 16);
stopEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
let stopEditingAuditsHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing audits."), stopEditingAuditsNavigationItem);
stopEditingAuditsHelpElement.classList.add("stop-editing-audits");
contentPlaceholder.appendChild(stopEditingAuditsHelpElement);
} else {
let hasEnabledAudit = WI.auditManager.tests.length && WI.auditManager.tests.some((test) => !test.disabled && test.supported);
let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected"));
contentView.element.appendChild(contentPlaceholder);
if (hasEnabledAudit) {
let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
descriptionElement.className = "description";
descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to view its results.");
}
let importAuditNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
importAuditNavigationItem.title = WI.UIString("Import audit or result");
importAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
importAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
let importAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import an audit or a result."), importAuditNavigationItem);
importAuditHelpElement.classList.add("import-audit");
contentPlaceholder.appendChild(importAuditHelpElement);
let startEditingAuditsNavigationItem = new WI.ButtonNavigationItem("start-editing-audits", WI.UIString("Edit"), "Images/Pencil.svg", 16, 16);
startEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
let startEditingAuditsHelpElement = WI.createNavigationItemHelp(hasEnabledAudit ? WI.UIString("Press %s to start editing audits.") : WI.UIString("Press %s to enable audits."), startEditingAuditsNavigationItem);
startEditingAuditsHelpElement.classList.add("start-editing-audits");
contentPlaceholder.appendChild(startEditingAuditsHelpElement);
}
let versionContainer = contentView.element.appendChild(document.createElement("div"));
versionContainer.classList.add("audit-version");
let version = WI.AuditTestBase.Version;
if (InspectorBackend.hasDomain("Audit"))
version = Math.min(version, InspectorBackend.getVersion("Audit"));
versionContainer.textContent = WI.UIString("Audit version: %s").format(version);
versionContainer.appendChild(WI.auditManager.editing ? WI.ReferencePage.AuditTab.EditingAudits.createLinkElement() : WI.ReferencePage.AuditTab.createLinkElement());
this.contentBrowser.showContentView(contentView);
}
// Popover delegate
willDismissPopover(popover)
{
console.assert(popover instanceof WI.CreateAuditPopover, popover);
let audit = popover.audit;
if (!audit) {
InspectorFrontendHost.beep();
return;
}
WI.auditManager.addTest(audit, {save: true});
WI.showRepresentedObject(audit);
}
// Protected
initialLayout()
{
super.initialLayout();
this.contentTreeOutline.allowsRepeatSelection = false;
let controlsNavigationBar = new WI.NavigationBar;
this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("start-stop-audit", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
this._startStopButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this);
controlsNavigationBar.addNavigationItem(this._startStopButtonNavigationItem);
this._createButtonNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.AuditNavigationSidebarPanel._createNavigationItemTitle(), "Images/Plus15.svg", 15, 15);
this._createButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._createButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this);
controlsNavigationBar.addNavigationItem(this._createButtonNavigationItem);
controlsNavigationBar.addNavigationItem(new WI.DividerNavigationItem);
let importButtonNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
importButtonNavigationItem.title = WI.UIString("Import audit or result");
importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
controlsNavigationBar.addNavigationItem(importButtonNavigationItem);
this.addSubview(controlsNavigationBar);
this._editButtonNavigationItem = new WI.ActivateButtonNavigationItem("edit-audits", WI.UIString("Edit"), WI.UIString("Done"), "Images/Pencil.svg", 16, 16);
this._editButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
this.filterBar.addFilterNavigationItem(this._editButtonNavigationItem);
for (let test of WI.auditManager.tests)
this._addTest(test);
WI.auditManager.results.forEach((result, i) => {
this._addResult(result, i);
});
this._updateControlNavigationItems();
this._updateEditNavigationItems();
this._updateNoAuditsPlaceholder();
WI.AuditTestGroup.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleAuditTestRemoved, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleAuditManagerEditingChanged, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.RunningStateChanged, this._handleAuditManagerRunningStateChanged, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.TestRemoved, this._handleAuditTestRemoved, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditTestScheduled, this);
this.contentTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
}
closed()
{
super.closed();
if (this.didInitialLayout) {
WI.auditManager.removeEventListener(WI.AuditManager.Event.EditingChanged, this._handleAuditManagerEditingChanged, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.RunningStateChanged, this._handleAuditManagerRunningStateChanged, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.TestRemoved, this._handleAuditTestRemoved, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditTestScheduled, this);
}
}
updateFilter()
{
super.updateFilter();
if (!this.hasActiveFilters)
this._updateNoAuditsPlaceholder();
}
hasCustomFilters()
{
return true;
}
matchTreeElementAgainstCustomFilters(treeElement, flags)
{
if (WI.auditManager.editing) {
if (treeElement.representedObject instanceof WI.AuditTestResultBase || treeElement.hasAncestor(this._resultsFolderTreeElement) || treeElement === this._resultsFolderTreeElement)
return false;
} else {
if (treeElement.representedObject instanceof WI.AuditTestBase && (treeElement.representedObject.disabled || !treeElement.representedObject.supported))
return false;
}
return super.matchTreeElementAgainstCustomFilters(treeElement, flags);
}
// Private
_addTest(test)
{
let treeElement = new WI.AuditTreeElement(test);
if (this._resultsFolderTreeElement) {
this.contentTreeOutline.insertChild(treeElement, this.contentTreeOutline.children.indexOf(this._resultsFolderTreeElement));
this._resultsFolderTreeElement.hidden = !this._resultsFolderTreeElement.children.length || WI.auditManager.editing;
} else
this.contentTreeOutline.appendChild(treeElement);
}
_addResult(result, index)
{
this.element.classList.add("has-results");
if (!this._resultsFolderTreeElement) {
this._resultsFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Results"));
this.contentTreeOutline.appendChild(this._resultsFolderTreeElement);
}
this._resultsFolderTreeElement.expand();
let resultFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Run %d").format(index + 1));
if (result instanceof WI.AuditTestResultBase) {
resultFolderTreeElement.subtitle = WI.UIString("Imported");
result = [result];
}
this._resultsFolderTreeElement.appendChild(resultFolderTreeElement);
console.assert(this._resultsFolderTreeElement.children.length === WI.auditManager.results.length);
for (let resultItem of result)
resultFolderTreeElement.appendChild(new WI.AuditTreeElement(resultItem));
}
_updateControlNavigationItems()
{
this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState === WI.AuditManager.RunningState.Active || WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping;
this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.some((test) => !test.disabled && test.supported) && (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive || WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
this._startStopButtonNavigationItem.hidden = WI.auditManager.editing;
this._createButtonNavigationItem.hidden = !WI.auditManager.editing;
}
_updateEditNavigationItems()
{
this._editButtonNavigationItem.label = WI.auditManager.editing ? this._editButtonNavigationItem.activatedToolTip : this._editButtonNavigationItem.defaultToolTip;
this._editButtonNavigationItem.activated = WI.auditManager.editing;
this._editButtonNavigationItem.enabled = WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive;
}
_updateNoAuditsPlaceholder()
{
if (WI.auditManager.editing || WI.auditManager.tests.some((test) => !test.disabled && test.supported)) {
if (!this.hasActiveFilters)
this.hideEmptyContentPlaceholder();
return;
}
let contentPlaceholder = this.showEmptyContentPlaceholder(WI.UIString("No Enabled Audits"));
contentPlaceholder.classList.add("no-enabled-audits");
if (WI.auditManager.results.length) {
// Move the placeholder to be the first element in the content area, where it will
// be styled so that it doesn't obstruct the results elements.
this.contentView.element.insertBefore(contentPlaceholder, this.contentView.element.firstChild);
}
}
_handleAuditManagerEditingChanged(event)
{
let previousSelectedTreeElement = this.contentTreeOutline.selectedTreeElement;
if (previousSelectedTreeElement) {
if (WI.auditManager.editing) {
if (!(previousSelectedTreeElement.representedObject instanceof WI.AuditTestBase))
previousSelectedTreeElement.deselect();
} else {
if (previousSelectedTreeElement.representedObject.disabled || !previousSelectedTreeElement.representedObject.supported)
previousSelectedTreeElement.deselect();
}
}
this.updateFilter();
if (!this.contentTreeOutline.selectedTreeElement)
this.showDefaultContentView();
this._updateControlNavigationItems();
this._updateEditNavigationItems();
this._updateNoAuditsPlaceholder();
}
_handleAuditManagerRunningStateChanged(event)
{
this._updateControlNavigationItems();
this._updateEditNavigationItems();
}
_handleAuditTestAdded(event)
{
let {test} = event.data;
this._addTest(test);
this._updateControlNavigationItems();
this._updateNoAuditsPlaceholder();
}
_handleAuditTestCompleted(event)
{
let {result, index} = event.data;
this._addResult(result, index);
this._updateControlNavigationItems();
this._updateEditNavigationItems();
}
_handleAuditTestRemoved(event)
{
console.assert(WI.auditManager.editing);
let {test} = event.data;
let treeElement = this.treeElementForRepresentedObject(test);
treeElement.parent.removeChild(treeElement);
this._updateControlNavigationItems();
}
_handleAuditTestScheduled(event)
{
this._updateControlNavigationItems();
this._updateEditNavigationItems();
}
_treeSelectionDidChange(event)
{
if (!this.selected)
return;
let treeElement = this.contentTreeOutline.selectedTreeElement;
if (!treeElement || treeElement instanceof WI.FolderTreeElement) {
this.showDefaultContentView();
return;
}
let representedObject = treeElement.representedObject;
if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup
|| representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult) {
WI.showRepresentedObject(representedObject);
return;
}
console.error("Unknown tree element", treeElement);
}
_handleStartStopButtonNavigationItemClicked(event)
{
if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive)
WI.auditManager.start();
else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active)
WI.auditManager.stop();
}
_handleCreateButtonNavigationItemClicked(event)
{
console.assert(WI.auditManager.editing);
let popover = new WI.CreateAuditPopover(this);
popover.show(event.target.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
}
_handleImportButtonNavigationItemClicked(event)
{
WI.FileUtilities.importJSON((result) => WI.auditManager.processJSON(result), {multiple: true});
}
_handleEditButtonNavigationItemClicked(event)
{
WI.auditManager.editing = !WI.auditManager.editing;
}
};
+140
View File
@@ -0,0 +1,140 @@
/*
* 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.AuditTabContentView = class AuditTabContentView extends WI.ContentBrowserTabContentView
{
constructor()
{
super(AuditTabContentView.tabInfo(), {
navigationSidebarPanelConstructor: WI.AuditNavigationSidebarPanel,
hideBackForwardButtons: true,
});
this._startStopShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Space, this._handleSpace.bind(this));
this._startStopShortcut.implicitlyPreventsDefault = false;
this._startStopShortcut.disabled = true;
}
// Static
static tabInfo()
{
return {
identifier: AuditTabContentView.Type,
image: "Images/Audit.svg",
displayName: WI.UIString("Audit", "Audit Tab Name", "Name of Audit Tab"),
};
}
static isTabAllowed()
{
return WI.sharedApp.isWebDebuggable();
}
// Public
get type()
{
return WI.AuditTabContentView.Type;
}
get supportsSplitContentBrowser()
{
return true;
}
canShowRepresentedObject(representedObject)
{
return representedObject instanceof WI.AuditTestCase
|| representedObject instanceof WI.AuditTestGroup
|| representedObject instanceof WI.AuditTestCaseResult
|| representedObject instanceof WI.AuditTestGroupResult;
}
attached()
{
super.attached();
this._startStopShortcut.disabled = false;
}
detached()
{
this._startStopShortcut.disabled = true;
super.detached();
}
// DropZoneView delegate
dropZoneShouldAppearForDragEvent(dropZone, event)
{
return event.dataTransfer.types.includes("Files");
}
dropZoneHandleDrop(dropZone, event)
{
let files = event.dataTransfer.files;
if (files.length !== 1) {
InspectorFrontendHost.beep();
return;
}
WI.FileUtilities.readJSON(files, (result) => WI.auditManager.processJSON(result));
}
// Protected
initialLayout()
{
super.initialLayout();
let dropZoneView = new WI.DropZoneView(this);
dropZoneView.text = WI.UIString("Import Audit or Result");
dropZoneView.targetElement = this.element;
this.addSubview(dropZoneView);
WI.auditManager.loadStoredTests();
}
// Private
_handleSpace(event)
{
if (WI.isEventTargetAnEditableField(event))
return;
if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive)
WI.auditManager.start(this.contentBrowser.currentRepresentedObjects);
else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active)
WI.auditManager.stop();
else
return;
event.preventDefault();
}
};
WI.AuditTabContentView.Type = "audit";
@@ -0,0 +1,172 @@
/*
* Copyright (C) 2018-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.
*/
.content-view-container > .content-view.audit-test-case {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.content-view-container > .content-view.audit-test-case > header {
flex-shrink: 0;
position: sticky;
top: 0;
z-index: var(--z-index-header);
background-color: var(--audit-test-header-background-color);
-webkit-backdrop-filter: blur(20px);
}
.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img {
width: 0.75em;
min-width: 12px;
height: 0.75em;
min-height: 12px;
margin: 0.125em;
margin-inline-end: 0.375em;
}
.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child {
margin-top: var(--audit-test-vertical-space);
}
.content-view-container > .content-view.audit-test-case > section {
overflow-y: scroll;
}
.content-view-container > .content-view.audit-test-case > section,
.content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror) {
height: 100%;
}
.content-view.audit-test-case > header {
padding-inline-end: calc(var(--audit-test-horizontal-space) / 2);
}
.content-view.audit-test-case > header h1 {
display: flex;
align-items: center;
}
.content-view.audit-test-case > header h1 > img {
width: 1em;
height: 1em;
min-width: 16px;
min-height: 16px;
margin-inline-end: 0.25em;
}
.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img {
opacity: 0.3;
pointer-events: none;
}
.content-view.audit-test-case > header > .metadata {
display: flex;
align-items: center;
text-align: end;
}
.content-view.audit-test-case > header > .metadata > .source {
margin-inline-end: 3px;
}
.content-view.audit-test-case > header > .metadata > .source > time {
display: block;
font-style: italic;
white-space: nowrap;
}
.content-view.audit-test-case > header > .metadata > .source > a {
display: block;
}
.content-view.audit-test-case > header > .metadata > .duration {
display: inline-block;
min-width: var(--metadata-width);
margin-inline-start: var(--audit-test-horizontal-space);
font-size: 12px;
text-align: center;
font-weight: bold;
}
.content-view.audit-test-case > section > :not(.message-text-view, .editor) {
margin-right: var(--audit-test-horizontal-space);
margin-left: var(--audit-test-horizontal-space);
}
.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child {
margin-bottom: var(--audit-test-vertical-space);
}
.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor) {
margin-top: var(--audit-test-vertical-space);
}
.content-view.audit-test-case > section h1 {
margin-bottom: 4px;
}
.content-view.audit-test-case > section table {
width: 100%;
border-collapse: collapse;
}
.content-view.audit-test-case > section table > tr + tr > td {
padding-top: 2px;
}
.content-view.audit-test-case > section table > tr > td > :not(.tree-outline) {
-webkit-user-select: text;
}
.content-view.audit-test-case > section table > tr > td:first-child {
font-family: -webkit-system-font, sans-serif;
font-size: 11px;
font-variant-numeric: tabular-nums;
text-align: end;
vertical-align: top;
color: var(--console-secondary-text-color);
}
.content-view.audit-test-case > section > .dom-nodes > table > tr > td:first-child {
position: relative;
top: -1px;
}
.content-view.audit-test-case > section table > tr > td + td {
width: 100%;
}
.content-view.audit-test-case > section .mark {
background-color: hsla(53, 83%, 53%, 0.2);
border-bottom: 1px solid hsl(47, 82%, 60%);
}
@media (prefers-color-scheme: dark) {
.content-view.audit-test-case.manager-editing > header h1 > img {
filter: var(--filter-invert);
}
}
+354
View File
@@ -0,0 +1,354 @@
/*
* 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.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTestContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestCaseResult);
super(representedObject);
this.element.classList.add("audit-test-case");
this._resultDataGeneralContainer = null;
this._resultDataDOMNodesContainer = null;
this._resultDataErrorsContainer = null;
}
// Protected
initialLayout()
{
super.initialLayout();
let informationContainer = this.headerView.element.appendChild(document.createElement("div"));
informationContainer.classList.add("information");
let nameContainer = informationContainer.appendChild(document.createElement("h1"));
this._resultImageElement = nameContainer.appendChild(document.createElement("img"));
nameContainer.appendChild(this.createNameElement("span"));
informationContainer.appendChild(this.createDescriptionElement("p"));
if (this.representedObject instanceof WI.AuditTestCase)
informationContainer.appendChild(this.createControlsTableElement());
this._metadataElement = this.headerView.element.appendChild(document.createElement("div"));
this._metadataElement.classList.add("metadata");
}
layout()
{
if (this.layoutReason !== WI.View.LayoutReason.Dirty)
return;
super.layout();
this._metadataElement.removeChildren();
this.contentView.element.removeChildren();
if (WI.auditManager.editing) {
this._resultImageElement.src = "Images/Pencil.svg";
this._resultImageElement.title = WI.UIString("Editing audit", "Editing Audit @ Audit Tab - Test Case", "Title of icon indiciating that the selected audit is being edited.");
let testEditorElement = this.contentView.element.appendChild(document.createElement("div"));
testEditorElement.className = "editor";
// Give the rest of the view a chance to load.
setTimeout(() => {
this._testCodeMirror = WI.CodeMirrorEditor.create(testEditorElement, {
autoCloseBrackets: true,
lineNumbers: true,
lineWrapping: true,
matchBrackets: true,
mode: "text/javascript",
readOnly: this.representedObject.editable ? false : "nocursor",
styleSelectedText: true,
value: this.representedObject.test,
});
});
return;
}
this._resultImageElement.src = "Images/AuditTestNoResult.svg";
this._resultImageElement.title = WI.UIString("Not yet run", "Not yet run @ Audit Tab - Test Case", "Title of icon indicating that the selected audit has not been run yet.");
let result = this.representedObject.result;
if (!result) {
if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive)
this.showNoResultPlaceholder();
else if (this.representedObject.runningState === WI.AuditManager.RunningState.Active)
this.showRunningPlaceholder();
else if (this.representedObject.runningState === WI.AuditManager.RunningState.Stopping)
this.showStoppingPlaceholder();
return;
}
if (result.didError) {
this._resultImageElement.src = "Images/AuditTestError.svg";
this._resultImageElement.title = WI.UIString("Error", "Error @ Audit Tab - Test Case", "Title of icon indicating that the selected audit threw an error.");
} else if (result.didFail) {
this._resultImageElement.src = "Images/AuditTestFail.svg";
this._resultImageElement.title = WI.UIString("Fail", "Fail @ Audit Tab - Test Case", "Title of icon indicating that the selected audit failed.");
} else if (result.didWarn) {
this._resultImageElement.src = "Images/AuditTestWarn.svg";
this._resultImageElement.title = WI.UIString("Warn", "Warn @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with issues (i.e. warnings).");
} else if (result.didPass) {
this._resultImageElement.src = "Images/AuditTestPass.svg";
this._resultImageElement.title = WI.UIString("Pass", "Pass @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with no issues.");
} else if (result.unsupported) {
this._resultImageElement.src = "Images/AuditTestUnsupported.svg";
this._resultImageElement.title = WI.UIString("Unsupported", "Unsupported @ Audit Tab - Test Case", "Title of icon indicating that the selected audit is not able to be run (i.e. unsupported).");
}
let metadata = result.metadata;
if (metadata) {
let sourceContainer = this._metadataElement.appendChild(document.createElement("div"));
sourceContainer.classList.add("source");
if (metadata.startTimestamp) {
let timeElement = sourceContainer.appendChild(document.createElement("time"));
timeElement.datetime = metadata.startTimestamp.toISOString();
timeElement.textContent = metadata.startTimestamp.toLocaleString();
if (metadata.endTimestamp) {
let totalDuration = Number.secondsToString((metadata.endTimestamp - metadata.startTimestamp) / 1000);
let durationElement = this._metadataElement.appendChild(document.createElement("span"));
durationElement.classList.add("duration");
durationElement.textContent = totalDuration;
if (metadata.asyncTimestamp) {
let evalDuration = Number.secondsToString((metadata.asyncTimestamp - metadata.startTimestamp) / 1000);
let asyncDuration = Number.secondsToString((metadata.endTimestamp - metadata.asyncTimestamp) / 1000);
durationElement.classList.add("async");
durationElement.title = WI.UIString("%s eval\n%s async").format(evalDuration, asyncDuration);
}
}
}
if (metadata.url && (metadata.url !== WI.networkManager.mainFrame.url || this.representedObject instanceof WI.AuditTestResultBase)) {
let url = new URL(metadata.url);
let origin = url.origin;
if (url.pathname.startsWith("/"))
origin += "/";
let linkElement = WI.linkifyURLAsNode(url.href, origin + url.href.substring(origin.length).truncateStart(20));
linkElement.title = url.href;
sourceContainer.appendChild(linkElement);
}
}
let resultData = result.data;
if (!this._resultDataGeneralContainer) {
let nonSpecialData = Object.filter(resultData, (key) => key !== "domNodes" && key !== "errors");
if (!isEmptyObject(nonSpecialData)) {
this._resultDataGeneralContainer = document.createElement("div");
let expression = "(" + JSON.stringify(nonSpecialData) + ")";
const options = {
objectGroup: WI.AuditTestBase.ObjectGroup,
doNotPauseOnExceptionsAndMuteConsole: true,
};
WI.runtimeManager.evaluateInInspectedWindow(expression, options, (nonSpecialDataRemoteObject, wasThrown) => {
console.assert(!wasThrown);
if (!nonSpecialDataRemoteObject)
return;
if (!this.representedObject.result || this.representedObject.result.data !== resultData)
return;
const propertyPath = null;
const forceExpanding = true;
let element = WI.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject(nonSpecialDataRemoteObject, propertyPath, forceExpanding);
let objectTree = element.__objectTree;
if (objectTree) {
objectTree.showOnlyJSON();
objectTree.expand();
}
this._resultDataGeneralContainer.appendChild(element);
this.hidePlaceholder();
});
}
}
if (this._resultDataGeneralContainer)
this.contentView.element.appendChild(this._resultDataGeneralContainer);
if (!this._resultDataDOMNodesContainer && resultData.domNodes && resultData.domNodes.length) {
this._resultDataDOMNodesContainer = document.createElement("div");
this._resultDataDOMNodesContainer.classList.add("dom-nodes");
let domNodeText = this._resultDataDOMNodesContainer.appendChild(document.createElement("h1"));
domNodeText.textContent = WI.UIString("DOM Nodes:");
let tableContainer = this._resultDataDOMNodesContainer.appendChild(document.createElement("table"));
resultData.domNodes.forEach((domNode, index) => {
domNode = result.resolvedDOMNodes[index] || domNode;
let rowElement = tableContainer.appendChild(document.createElement("tr"));
let indexElement = rowElement.appendChild(document.createElement("td"));
indexElement.textContent = index + 1;
let dataElement = rowElement.appendChild(document.createElement("td"));
if (domNode instanceof WI.DOMNode) {
let treeOutline = new WI.DOMTreeOutline({selectable: false});
treeOutline.setVisible(true);
treeOutline.rootDOMNode = domNode;
let rootTreeElement = treeOutline.children[0];
rootTreeElement.showGoToArrow = true;
if (!rootTreeElement.hasChildren)
treeOutline.element.classList.add("single-node");
if (resultData.domAttributes) {
for (let domAttribute of resultData.domAttributes) {
rootTreeElement.highlightAttribute(domAttribute);
rootTreeElement.updateTitle();
}
}
dataElement.appendChild(treeOutline.element);
} else if (typeof domNode === "string") {
let codeMirror = WI.CodeMirrorEditor.create(dataElement.appendChild(document.createElement("code")), {
mode: "css",
readOnly: true,
lineWrapping: true,
styleSelectedText: true,
});
codeMirror.setValue(domNode);
if (resultData.domAttributes) {
for (let domAttribute of resultData.domAttributes) {
let regex = null;
if (domAttribute === "id")
regex = /(\#[^\#|\.|\[|\s|$]+)/g;
else if (domAttribute === "class")
regex = /(\.[^\#|\.|\[|\s|$]+)/g;
else
regex = new RegExp(`\\[\\s*(${domAttribute})\\s*=`, "g");
while (true) {
let match = regex.exec(domNode);
if (!match)
break;
let start = match.index + match[0].indexOf(match[1]);
codeMirror.markText({line: 0, ch: start}, {line: 0, ch: start + match[1].length}, {className: "mark"});
}
}
}
setTimeout(() => {
codeMirror.refresh();
});
}
});
}
if (this._resultDataDOMNodesContainer)
this.contentView.element.appendChild(this._resultDataDOMNodesContainer);
if (!this._resultDataErrorsContainer && resultData.errors && resultData.errors.length) {
this._resultDataErrorsContainer = document.createElement("div");
this._resultDataErrorsContainer.classList.add("errors");
let errorText = this._resultDataErrorsContainer.appendChild(document.createElement("h1"));
errorText.textContent = WI.UIString("Errors:");
let tableContainer = this._resultDataErrorsContainer.appendChild(document.createElement("table"));
resultData.errors.forEach((error, index) => {
let rowElement = tableContainer.appendChild(document.createElement("tr"));
let indexElement = rowElement.appendChild(document.createElement("td"));
indexElement.textContent = index + 1;
let dataElement = rowElement.appendChild(document.createElement("td"));
let errorElement = dataElement.appendChild(document.createElement("div"));
errorElement.classList.add("error");
errorElement.textContent = error;
});
}
if (this._resultDataErrorsContainer)
this.contentView.element.appendChild(this._resultDataErrorsContainer);
if (!this.contentView.element.children.length)
this.showNoResultDataPlaceholder();
}
handleResultChanged(event)
{
super.handleResultChanged(event);
this._resultDataGeneralContainer = null;
this._resultDataDOMNodesContainer = null;
this._resultDataErrorsContainer = null;
}
saveEditedData()
{
super.saveEditedData();
this.representedObject.test = this._testCodeMirror.getValue().trim();
}
showRunningPlaceholder()
{
if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) {
this.placeholderElement = WI.createMessageTextView(WI.UIString("Running the \u201C%s\u201D audit").format(this.representedObject.name));
this.placeholderElement.__placeholderRunning = true;
let spinner = new WI.IndeterminateProgressSpinner;
this.placeholderElement.appendChild(spinner.element);
let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13);
stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, function(event) {
WI.auditManager.stop();
}, stopAuditNavigationItem);
let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem);
this.placeholderElement.appendChild(stopAuditHelpElement);
this.placeholderElement.appendChild(WI.ReferencePage.AuditTab.RunningAudits.createLinkElement());
}
super.showRunningPlaceholder();
}
};
+191
View File
@@ -0,0 +1,191 @@
/*
* Copyright (C) 2018-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.
*/
.content-view-container > .content-view.audit-test {
overflow-y: auto;
}
.content-view-container > .content-view.audit-test > header {
padding-top: calc(var(--audit-test-vertical-space) / 5 * 8);
padding-bottom: calc(var(--audit-test-vertical-space) / 5 * 8);
border-bottom: 1px solid var(--border-color);
}
.content-view-container > .content-view.audit-test > header h1 {
font-size: 2.8em;
}
.content-view-container > .content-view.audit-test > header p {
font-size: 1.25em;
opacity: 0.85;
}
.content-view.audit-test {
--audit-test-vertical-space: 10px;
--audit-test-horizontal-space: 20px;
--audit-test-header-background-color: hsla(0, 0%, 98%, 0.7);
}
.content-view.audit-test:is(.unsupported, .disabled):not(.manager-editing) {
display: none;
}
.content-view.audit-test h1 {
margin: 0;
}
.content-view.audit-test.manager-editing .editor:not(:empty) {
width: 100%;
}
.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty) {
border: 1px solid var(--border-color);
}
.content-view.audit-test .CodeMirror {
width: 100%;
height: auto;
}
.content-view.audit-test > header {
display: flex;
align-items: center;
padding-top: var(--audit-test-vertical-space);
padding-bottom: var(--audit-test-vertical-space);
padding-inline-start: var(--audit-test-horizontal-space);
--metadata-width: 60px;
}
.content-view.audit-test > header > .information {
flex-grow: 1;
}
.content-view.audit-test > header p {
margin: 4px 0 0;
}
.content-view.audit-test > header :is(.name, .description):not([contenteditable]) {
padding: 1px;
outline: none;
}
.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable] {
border: 1px solid var(--border-color);
-webkit-user-select: text;
outline-offset: var(--focus-ring-outline-offset);
}
.content-view.audit-test.manager-editing > header .name[contenteditable]:empty {
border-color: var(--error-text-color);
}
.content-view.audit-test.manager-editing > header .name[contenteditable]:empty:before {
content: attr(data-name);
color: var(--text-color-tertiary);
}
.content-view.audit-test.manager-editing > header .description[contenteditable]:empty:before {
content: "description";
color: var(--text-color-tertiary);
}
.content-view.audit-test:not(.manager-editing) > header .description:empty,
.content-view.audit-test:not(.manager-editing) > header table.controls {
display: none;
}
.content-view.audit-test > header table.controls,
.content-view.audit-test > header table.controls > tr > td {
width: 100%;
}
.content-view.audit-test > header table.controls > tr > th {
text-align: end;
vertical-align: top;
line-height: 2em;
color: hsl(0, 0%, 34%);
}
.content-view.audit-test > header table.controls > tr.supports input[type="number"] {
text-align: end;
}
.content-view.audit-test > header table.controls > tr.supports .warning {
margin-inline-start: 4px;
color: var(--text-color-secondary);
}
.content-view.audit-test > header table.controls > tr.supports .warning:not(:empty)::before {
display: inline-block;
width: 1em;
margin-inline-end: 2px;
vertical-align: -1px;
content: url(../Images/Warning.svg);
}
.content-view.audit-test > header table.controls > tr.setup .editor {
margin-top: 2px;
}
.content-view.audit-test .audit-test.filtered,
.content-view.audit-test .audit-test .message-text-view {
display: none;
}
.content-view.audit-test > section {
position: relative;
}
.content-view.audit-test > section > .message-text-view {
background-color: var(--background-color-content);
}
.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner) {
margin-bottom: 8px;
}
.content-view.audit-test.showing-placeholder {
display: flex;
flex-direction: column;
}
.content-view.audit-test.showing-placeholder > section {
flex-grow: 1;
}
.content-view.audit-test.showing-placeholder > section > :not(.message-text-view) {
display: none;
}
@media (prefers-color-scheme: dark) {
.content-view.audit-test {
--audit-test-header-background-color: hsla(0, 0%, 23%, 0.7);
}
.content-view.audit-test > header table.controls > tr > th {
color: var(--text-color-secondary);
}
}
+629
View File
@@ -0,0 +1,629 @@
/*
* 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.AuditTestContentView = class AuditTestContentView extends WI.ContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.AuditTestBase || representedObject instanceof WI.AuditTestResultBase);
super(representedObject);
// This class should not be instantiated directly. Create a concrete subclass instead.
console.assert(this.constructor !== WI.AuditTestContentView && this instanceof WI.AuditTestContentView);
this.element.classList.add("audit-test");
if (this.representedObject.editable)
this.element.classList.add("editable");
if (WI.FileUtilities.canSave(WI.FileUtilities.SaveMode.FileVariants))
this._saveMode = WI.FileUtilities.SaveMode.FileVariants;
else if (WI.FileUtilities.canSave(WI.FileUtilities.SaveMode.SingleFile))
this._saveMode = WI.FileUtilities.SaveMode.SingleFile;
else
this._saveMode = null;
switch (this._saveMode) {
case WI.FileUtilities.SaveMode.SingleFile:
if (this.representedObject instanceof WI.AuditTestBase) {
this._exportTestButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-test", WI.UIString("Export Audit"), "Images/Export.svg", 15, 15);
this._exportTestButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._exportTestButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._exportTestButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportTestButtonNavigationItemClicked, this);
}
this._exportResultButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-result", WI.UIString("Export Result"), "Images/Export.svg", 15, 15);
this._exportResultButtonNavigationItem.tooltip = WI.UIString("Export result (%s)", "Export result (%s) @ Audit Tab", "Tooltip for button that exports the most recent result after running an audit.").format(WI.saveKeyboardShortcut.displayName);
this._exportResultButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._exportResultButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._exportResultButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportResultButtonNavigationItemClicked, this);
break;
case WI.FileUtilities.SaveMode.FileVariants:
this._exportButtonNavigationItem = new WI.ButtonNavigationItem("audit-export", WI.UIString("Export"), "Images/Export.svg", 15, 15);
this._exportButtonNavigationItem.tooltip = WI.UIString("Export (%s)").format(WI.saveKeyboardShortcut.displayName);
this._exportButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportButtonNavigationItemClicked, this);
break;
}
this._updateExportNavigationItems();
this._headerView = new WI.View(document.createElement("header"));
this._contentView = new WI.View(document.createElement("section"));
this._placeholderElement = null;
this._cachedName = this.representedObject.name;
this._nameElement = null;
this._descriptionElement = null;
this._supportsInputElement = null;
this._supportsWarningElement = null;
this._shownResult = null;
}
// Public
get navigationItems()
{
let navigationItems = [];
if (this._exportTestButtonNavigationItem)
navigationItems.push(this._exportTestButtonNavigationItem);
if (this._exportResultButtonNavigationItem)
navigationItems.push(this._exportResultButtonNavigationItem);
if (this._exportButtonNavigationItem)
navigationItems.push(this._exportButtonNavigationItem);
return navigationItems;
}
// Protected
get headerView() { return this._headerView; }
get contentView() { return this._contentView; }
get supportsSave()
{
return !!this._saveMode && !WI.auditManager.editing && !!this.representedObject.result;
}
get saveMode()
{
return this._saveMode;
}
get saveData()
{
return {customSaveHandler: () => { this._export(); }};
}
get result()
{
if (this.representedObject instanceof WI.AuditTestBase)
return this.representedObject;
return this.representedObject.result;
}
createNameElement(tagName)
{
console.assert(!this._nameElement);
this._nameElement = document.createElement(tagName);
this._nameElement.textContent = this.representedObject.name;
this._nameElement.className = "name";
if (this.representedObject.editable) {
this._nameElement.spellcheck = false;
this._nameElement.addEventListener("keydown", (event) => {
this._handleEditorKeydown(event, this._descriptionElement);
});
this._nameElement.addEventListener("input", (event) => {
console.assert(WI.auditManager.editing);
let name = this._nameElement.textContent;
if (!name.trim()) {
name = this._cachedName;
this._nameElement.removeChildren();
}
this.representedObject.name = name;
});
}
return this._nameElement;
}
createDescriptionElement(tagName)
{
console.assert(!this._descriptionElement);
this._descriptionElement = document.createElement(tagName);
this._descriptionElement.textContent = this.representedObject.description;
this._descriptionElement.className = "description";
if (this.representedObject.editable) {
this._descriptionElement.spellcheck = false;
this._descriptionElement.addEventListener("keydown", (event) => {
this._handleEditorKeydown(event, this._supportsInputElement);
});
this._descriptionElement.addEventListener("input", (event) => {
console.assert(WI.auditManager.editing);
let description = this._descriptionElement.textContent;
if (!description.trim()) {
description = "";
this._descriptionElement.removeChildren();
}
this.representedObject.description = description;
});
}
return this._descriptionElement;
}
createControlsTableElement()
{
console.assert(this.representedObject instanceof WI.AuditTestBase);
console.assert(!this._supportsInputElement);
console.assert(!this._supportsWarningElement);
let controlsTableElement = document.createElement("table");
controlsTableElement.className = "controls";
let supportsRowElement = controlsTableElement.appendChild(document.createElement("tr"));
supportsRowElement.className = "supports";
let supportsHeaderElement = supportsRowElement.appendChild(document.createElement("th"));
supportsHeaderElement.textContent = WI.unlocalizedString("supports");
let supportsDataElement = supportsRowElement.appendChild(document.createElement("td"));
this._supportsInputElement = supportsDataElement.appendChild(document.createElement("input"));
this._supportsInputElement.type = "number";
this._supportsInputElement.disabled = !this.representedObject.editable;
this._supportsInputElement.min = 0;
this._supportsInputElement.placeholder = Math.min(WI.AuditTestBase.Version, InspectorBackend.hasDomain("Audit") ? InspectorBackend.getVersion("Audit") : Infinity);
if (!isNaN(this.representedObject.supports))
this._supportsInputElement.value = this.representedObject.supports;
if (this.representedObject.editable) {
this._supportsInputElement.addEventListener("keydown", (event) => {
this._handleEditorKeydown(event, this._setupEditorElement);
});
}
this._supportsWarningElement = supportsDataElement.appendChild(document.createElement("span"));
this._supportsWarningElement.className = "warning";
if (this.representedObject.topLevelTest === this.representedObject) {
let setupRowElement = controlsTableElement.appendChild(document.createElement("tr"));
setupRowElement.className = "setup";
let setupHeaderElement = setupRowElement.appendChild(document.createElement("th"));
setupHeaderElement.textContent = WI.unlocalizedString("setup");
let setupDataElement = setupRowElement.appendChild(document.createElement("td"));
this._setupEditorElement = setupDataElement.appendChild(document.createElement("div"));
}
if (this.representedObject.editable) {
this._supportsInputElement.addEventListener("input", (event) => {
this.representedObject.supports = parseInt(this._supportsInputElement.value);
this._updateSupportsInputState();
});
}
return controlsTableElement;
}
initialLayout()
{
super.initialLayout();
this.addSubview(this._headerView);
this.addSubview(this._contentView);
}
layout()
{
super.layout();
if (this.representedObject instanceof WI.AuditTestBase) {
this.element.classList.toggle("unsupported", !this.representedObject.supported);
this.element.classList.toggle("disabled", this.representedObject.disabled);
this.element.classList.toggle("manager-editing", WI.auditManager.editing);
if (this.representedObject.editable) {
let contentEditable = WI.auditManager.editing ? "plaintext-only" : "inherit";
this._nameElement.contentEditable = contentEditable;
this._descriptionElement.contentEditable = contentEditable;
}
if (WI.auditManager.editing) {
this._cachedName = this.representedObject.name;
this._nameElement.dataset.name = this._cachedName;
this._updateSupportsInputState();
this._createSetupEditor();
} else {
this._nameElement.textContent ||= this._cachedName;
this._setupEditorElement?.removeChildren();
}
}
this.hidePlaceholder();
this._updateExportNavigationItems();
}
attached()
{
super.attached();
if (this.representedObject instanceof WI.AuditTestBase) {
this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this);
}
}
detached()
{
if (this.representedObject instanceof WI.AuditTestBase) {
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this);
if (this.representedObject.editable && WI.auditManager.editing)
this.saveEditedData();
}
super.detached();
}
handleResultChanged(event)
{
// Overridden by sub-classes.
if (!WI.auditManager.editing)
this.needsLayout();
}
get placeholderElement()
{
return this._placeholderElement;
}
set placeholderElement(placeholderElement)
{
this.hidePlaceholder();
this._placeholderElement = placeholderElement;
}
saveEditedData()
{
console.assert(this.representedObject.editable, this.representedObject);
if (this._setupCodeMirror)
this.representedObject.setup = this._setupCodeMirror.getValue().trim();
}
showRunningPlaceholder()
{
// Overridden by sub-classes.
console.assert(this.placeholderElement);
this._showPlaceholder();
}
showStoppingPlaceholder()
{
if (!this.placeholderElement || !this.placeholderElement.__placeholderStopping) {
this.placeholderElement = WI.createMessageTextView(WI.UIString("Stopping the \u201C%s\u201D audit").format(this.representedObject.name));
this.placeholderElement.__placeholderStopping = true;
let spinner = new WI.IndeterminateProgressSpinner;
this.placeholderElement.appendChild(spinner.element);
this.placeholderElement.appendChild(WI.ReferencePage.AuditTab.RunningAudits.createLinkElement());
}
this._showPlaceholder();
}
showNoResultPlaceholder()
{
if (!this.placeholderElement || !this.placeholderElement.__placeholderNoResult) {
this.placeholderElement = WI.createMessageTextView(WI.UIString("No Result"));
this.placeholderElement.__placeholderNoResult = true;
let startNavigationItem = new WI.ButtonNavigationItem("run-audit", WI.UIString("Start"), "Images/AuditStart.svg", 15, 15);
startNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
startNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, function(event) {
WI.auditManager.start([this.representedObject]);
}, this);
let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit."), startNavigationItem);
this.placeholderElement.appendChild(importHelpElement);
this.placeholderElement.appendChild(WI.ReferencePage.AuditTab.RunningAudits.createLinkElement());
}
this._showPlaceholder();
}
showNoResultDataPlaceholder()
{
if (!this.placeholderElement || !this.placeholderElement.__placeholderNoResultData) {
let result = this.representedObject.result;
if (!result) {
this.showNoResultPlaceholder();
return;
}
let message = null;
if (result.didError)
message = WI.UIString("The \u201C%s\u201D audit threw an error");
else if (result.didFail)
message = WI.UIString("The \u201C%s\u201D audit failed");
else if (result.didWarn)
message = WI.UIString("The \u201C%s\u201D audit resulted in a warning");
else if (result.didPass)
message = WI.UIString("The \u201C%s\u201D audit passed");
else if (result.unsupported)
message = WI.UIString("The \u201C%s\u201D audit is unsupported");
else {
console.error("Unknown result", result);
return;
}
this.placeholderElement = WI.createMessageTextView(message.format(this.representedObject.name), result.didError);
this.placeholderElement.__placeholderNoResultData = true;
this.placeholderElement.appendChild(WI.ReferencePage.AuditTab.AuditResults.createLinkElement());
}
this._showPlaceholder();
}
showFilteredPlaceholder()
{
if (!this.placeholderElement || !this.placeholderElement.__placeholderFiltered) {
this.placeholderElement = WI.createMessageTextView(WI.UIString("No Filter Results"));
this.placeholderElement.__placeholderFiltered = true;
let buttonElement = this.placeholderElement.appendChild(document.createElement("button"));
buttonElement.textContent = WI.UIString("Clear Filters");
buttonElement.addEventListener("click", () => {
this.resetFilter();
this.needsLayout();
});
this.placeholderElement.appendChild(WI.ReferencePage.AuditTab.createLinkElement());
}
this._showPlaceholder();
}
hidePlaceholder()
{
this.element.classList.remove("showing-placeholder");
if (this.placeholderElement)
this.placeholderElement.remove();
}
applyFilter(levels)
{
let hasMatch = false;
for (let view of this.contentView.subviews) {
let matches = view.applyFilter(levels);
view.element.classList.toggle("filtered", !matches);
if (matches)
hasMatch = true;
}
this.element.classList.toggle("no-matches", !hasMatch);
if (!Array.isArray(levels))
return true;
let result = this.representedObject.result;
if (!result)
return false;
if ((levels.includes(WI.AuditTestCaseResult.Level.Error) && result.didError)
|| (levels.includes(WI.AuditTestCaseResult.Level.Fail) && result.didFail)
|| (levels.includes(WI.AuditTestCaseResult.Level.Warn) && result.didWarn)
|| (levels.includes(WI.AuditTestCaseResult.Level.Pass) && result.didPass)
|| (levels.includes(WI.AuditTestCaseResult.Level.Unsupported) && result.unsupported)) {
return true;
}
return false;
}
resetFilter()
{
for (let view of this.contentView.subviews)
view.element.classList.remove("filtered");
this.element.classList.remove("no-matches");
}
// Private
_export()
{
let object = this.representedObject;
if (this._saveMode === WI.FileUtilities.SaveMode.SingleFile)
object = object.result;
WI.auditManager.export(this._saveMode, object);
}
_updateExportNavigationItems()
{
if (this._exportTestButtonNavigationItem)
this._exportTestButtonNavigationItem.enabled = !WI.auditManager.editing;
if (this._exportResultButtonNavigationItem)
this._exportResultButtonNavigationItem.enabled = !WI.auditManager.editing && this.representedObject.result;
if (this._exportButtonNavigationItem)
this._exportButtonNavigationItem.enabled = this._saveMode && !WI.auditManager.editing;
}
_updateSupportsInputState()
{
console.assert(WI.auditManager.editing);
this._supportsInputElement.autosize(4);
this._supportsWarningElement.removeChildren();
if (this.representedObject.supports > WI.AuditTestBase.Version)
this._supportsWarningElement.textContent = WI.UIString("too new to run in this Web Inspector", "too new to run in this Web Inspector @ Audit Tab", "Warning text shown if the version number in the 'supports' input is too new.");
else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit"))
this._supportsWarningElement.textContent = WI.UIString("too new to run in the inspected page", "too new to run in the inspected page @ Audit Tab", "Warning text shown if the version number in the 'supports' input is too new.");
}
_createSetupEditor()
{
if (!this._setupEditorElement)
return;
let setupEditorElement = document.createElement(this._setupEditorElement.nodeName);
setupEditorElement.className = "editor";
// Give the rest of the view a chance to load.
setTimeout(() => {
this._setupCodeMirror = WI.CodeMirrorEditor.create(setupEditorElement, {
autoCloseBrackets: true,
lineNumbers: true,
lineWrapping: true,
matchBrackets: true,
mode: "text/javascript",
readOnly: this.representedObject.editable ? false : "nocursor",
styleSelectedText: true,
value: this.representedObject.setup,
});
});
this._setupEditorElement.parentNode.replaceChild(setupEditorElement, this._setupEditorElement);
this._setupEditorElement = setupEditorElement;
}
_showPlaceholder()
{
this.element.classList.add("showing-placeholder");
this.contentView.element.appendChild(this.placeholderElement);
}
_handleEditorKeydown(event, nextEditor)
{
console.assert(WI.auditManager.editing);
switch (event.keyCode) {
case WI.KeyboardShortcut.Key.Enter.keyCode:
if (nextEditor) {
nextEditor.focus();
break;
}
// fallthrough
case WI.KeyboardShortcut.Key.Escape.keyCode:
event.target.blur();
break;
default:
return;
}
event.preventDefault();
}
_handleExportTestButtonNavigationItemClicked(event)
{
WI.auditManager.export(WI.FileUtilities.SaveMode.SingleFile, this.representedObject);
}
_handleExportResultButtonNavigationItemClicked(event)
{
WI.auditManager.export(WI.FileUtilities.SaveMode.SingleFile, this.representedObject.result);
}
_handleExportButtonNavigationItemClicked(event)
{
WI.auditManager.export(WI.FileUtilities.SaveMode.FileVariants, this.representedObject);
}
_handleTestChanged(event)
{
this.needsLayout();
}
_handleTestDisabledChanged(event)
{
console.assert(WI.auditManager.editing);
this.element.classList.toggle("disabled", this.representedObject.disabled);
}
_handleTestSupportedChanged(event)
{
console.assert(WI.auditManager.editing);
this.element.classList.toggle("unsupported", !this.representedObject.supported);
}
_handleEditingChanged(event)
{
this.needsLayout();
// We only need to save changes when editing is done. No need to save it after entering the editing mode.
if (!WI.auditManager.editing && this.representedObject.editable)
this.saveEditedData();
this._updateExportNavigationItems();
}
};
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2018-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.
*/
.content-view-container > .content-view.audit-test-group > header {
background-color: var(--background-color-content);
}
.content-view.audit-test-group > section > .audit-test-group > header {
margin-top: -1px;
border-top: 1px solid var(--border-color);
}
.content-view.audit-test-group > header {
padding-inline-end: var(--audit-test-horizontal-space);
}
.content-view.audit-test-group.no-matches + .audit-test-group > header {
border-top: none;
}
.content-view.audit-test-group > header,
.content-view.audit-test-group:not(.filtered):last-child > header {
border-bottom: 1px solid var(--border-color);
}
.content-view.audit-test-group.contains-test-case > header {
position: sticky;
top: 0;
z-index: var(--z-index-header);
background-color: var(--audit-test-header-background-color);
-webkit-backdrop-filter: blur(20px);
}
.content-view.audit-test-group > section > .audit-test-group.contains-test-case > header {
top: -1px;
}
.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case,
.content-view.audit-test-group + .content-view.audit-test-case {
border-top: 1px solid var(--border-color);
}
.content-view.audit-test-group.contains-test-case:not(.contains-test-group) > section,
.content-view.audit-test-group.contains-test-case.contains-test-group > section > .audit-test-case {
padding-right: calc(var(--audit-test-horizontal-space) / 2);
padding-left: calc(var(--audit-test-horizontal-space) / 2);
}
.content-view.audit-test-group > header > .information > p {
font-size: 1.25em;
}
.content-view.audit-test-group > header > nav {
display: inline-flex;
height: auto;
border-bottom: none;
overflow: visible;
}
.content-view.audit-test-group > header > nav > .scope-bar {
overflow: visible;
}
.content-view.audit-test-group > header > nav:empty {
display: none;
}
.content-view.audit-test-group > header > nav > .scope-bar > li {
margin: 0 3px;
}
.content-view.audit-test-group > header > nav > .scope-bar > li:not(:hover, .selected) {
--scope-bar-border-color-override: var(--selected-background-color-unfocused);
}
.content-view.audit-test-group > header > nav > .scope-bar > li:last-child {
margin-inline-end: 0;
}
.content-view.audit-test-group > header > nav > .scope-bar > li > img {
width: 16px;
height: 16px;
margin-top: 2px;
margin-inline-end: 4px;
vertical-align: -4px;
}
.content-view.audit-test-group > header > nav > .scope-bar > li.pass > img {
content: url(../Images/AuditTestPass.svg);
}
.content-view.audit-test-group > header > nav > .scope-bar > li.warn > img {
content: url(../Images/AuditTestWarn.svg);
}
.content-view.audit-test-group > header > nav > .scope-bar > li.fail > img {
content: url(../Images/AuditTestFail.svg);
}
.content-view.audit-test-group > header > nav > .scope-bar > li.error > img {
content: url(../Images/AuditTestError.svg);
}
.content-view.audit-test-group > header > nav > .scope-bar > li.unsupported > img {
content: url(../Images/AuditTestUnsupported.svg);
}
.content-view.audit-test-group > header > .percentage-pass {
width: var(--metadata-width);
margin-inline-start: var(--audit-test-horizontal-space);
font-size: 16px;
text-align: center;
font-weight: bold;
/* FIXME: Use CSS4 color blend functions once they're available. */
color: hsla(0, 0%, 0%, 0.5);
}
.content-view.audit-test-group > header > .percentage-pass > span {
font-size: 24px;
/* FIXME: Use CSS4 color blend functions once they're available. */
color: hsla(0, 0%, 0%, 0.65);
}
.content-view.audit-test-group > section > .audit-test-case:first-child,
.content-view.audit-test-group > section > .audit-test-case.filtered ~ .audit-test-case:not(.filtered),
.content-view.audit-test-group > section > .audit-test-group + .audit-test-case,
.content-view.audit-test-group > section > .audit-test-case + .audit-test-group {
margin-top: var(--audit-test-vertical-space);
}
.content-view.audit-test-group > section > .audit-test-case:not(.filtered) ~ .audit-test-case:not(.filtered) {
margin-top: unset;
}
.content-view.audit-test-group > section > .audit-test-case:last-child {
margin-bottom: var(--audit-test-vertical-space);
}
@media (prefers-color-scheme: dark) {
.content-view.audit-test-group > header > .percentage-pass {
/* FIXME: Use CSS4 color blend functions once they're available. */
color: hsla(0, 0%, 88%, 0.5);
}
.content-view.audit-test-group > header > .percentage-pass > span {
/* FIXME: Use CSS4 color blend functions once they're available. */
color: hsla(0, 0%, 88%, 0.65);
}
}
@@ -0,0 +1,364 @@
/*
* 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.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditTestContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.AuditTestGroup || representedObject instanceof WI.AuditTestGroupResult);
super(representedObject);
this.element.classList.add("audit-test-group");
this._levelScopeBar = null;
this._viewForSubobject = new Map;
}
// Popover delegate
willDismissPopover(popover)
{
console.assert(popover instanceof WI.CreateAuditPopover, popover);
let audit = popover.audit;
if (!audit) {
InspectorFrontendHost.beep();
return;
}
this.representedObject.addTest(audit);
}
// Protected
createControlsTableElement()
{
let controlsTableElement = super.createControlsTableElement();
let actionsRowElement = controlsTableElement.appendChild(document.createElement("tr"));
actionsRowElement.className = "actions";
let actionsHeaderElement = controlsTableElement.appendChild(document.createElement("th"));
let actionsDataElement = controlsTableElement.appendChild(document.createElement("td"));
let addTestCaseButtonElement = actionsDataElement.appendChild(document.createElement("button"));
addTestCaseButtonElement.disabled = !this.representedObject.editable;
addTestCaseButtonElement.textContent = WI.UIString("Add Test Case", "Add Test Case @ Audit Tab - Group", "Text of button to add a new audit test case to the currently shown audit group.");
addTestCaseButtonElement.addEventListener("click", (event) => {
console.assert(WI.auditManager.editing);
let popover = new WI.CreateAuditPopover(this);
popover.show(addTestCaseButtonElement, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
});
return controlsTableElement;
}
initialLayout()
{
super.initialLayout();
let informationContainer = this.headerView.element.appendChild(document.createElement("div"));
informationContainer.classList.add("information");
let nameContainer = informationContainer.appendChild(document.createElement("h1"));
nameContainer.appendChild(this.createNameElement("span"));
informationContainer.appendChild(this.createDescriptionElement("p"));
if (this.representedObject instanceof WI.AuditTestGroup)
informationContainer.appendChild(this.createControlsTableElement());
this._levelNavigationBar = new WI.NavigationBar(document.createElement("nav"));
this.headerView.addSubview(this._levelNavigationBar);
this._percentageContainer = this.headerView.element.appendChild(document.createElement("div"));
this._percentageContainer.classList.add("percentage-pass");
this._percentageContainer.hidden = true;
this._percentageTextElement = document.createElement("span");
const format = WI.UIString("%s%%", "Percentage (of audits)", "The number of tests that passed expressed as a percentage, followed by a literal %.");
String.format(format, [this._percentageTextElement], String.standardFormatters, this._percentageContainer, (a, b) => {
a.append(b);
return a;
});
this._updateClassList();
}
layout()
{
if (this.layoutReason !== WI.View.LayoutReason.Dirty)
return;
super.layout();
if (WI.auditManager.editing) {
if (this._levelScopeBar) {
this._levelNavigationBar.removeNavigationItem(this._levelScopeBar);
this._levelScopeBar = null;
}
this._percentageContainer.hidden = true;
this.resetFilter();
return;
}
let result = this.representedObject.result;
if (!result) {
if (this._levelScopeBar) {
this._levelNavigationBar.removeNavigationItem(this._levelScopeBar);
this._levelScopeBar = null;
}
this._percentageContainer.hidden = true;
this._percentageTextElement.textContent = "";
if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive)
this.showNoResultPlaceholder();
else if (this.representedObject.runningState === WI.AuditManager.RunningState.Active)
this.showRunningPlaceholder();
else if (this.representedObject.runningState === WI.AuditManager.RunningState.Stopping)
this.showStoppingPlaceholder();
return;
}
let levelCounts = result.levelCounts;
let totalCount = Object.values(levelCounts).reduce((accumulator, current) => accumulator + current);
this._percentageTextElement.textContent = Math.floor(100 * levelCounts[WI.AuditTestCaseResult.Level.Pass] / totalCount);
this._percentageContainer.hidden = false;
if (!this._levelScopeBar) {
let scopeBarItems = [];
let addScopeBarItem = (level, labelSingular, labelPlural) => {
let count = levelCounts[level];
if (isNaN(count) || count <= 0)
return;
let label = (labelPlural && count !== 1) ? labelPlural : labelSingular;
let scopeBarItem = new WI.ScopeBarItem(level, label.format(count), {
className: level,
exclusive: false,
independent: true,
});
scopeBarItem.selected = true;
scopeBarItem.element.insertBefore(document.createElement("img"), scopeBarItem.element.firstChild);
scopeBarItems.push(scopeBarItem);
};
addScopeBarItem(WI.AuditTestCaseResult.Level.Pass, WI.UIString("%d Passed", "%d Passed (singular)", ""), WI.UIString("%d Passed", "%d Passed (plural)", ""));
addScopeBarItem(WI.AuditTestCaseResult.Level.Warn, WI.UIString("%d Warning"), WI.UIString("%d Warnings"));
addScopeBarItem(WI.AuditTestCaseResult.Level.Fail, WI.UIString("%d Failed", "%d Failed (singular)", ""), WI.UIString("%d Failed", "%d Failed (plural)", ""));
addScopeBarItem(WI.AuditTestCaseResult.Level.Error, WI.UIString("%d Error"), WI.UIString("%d Errors"));
addScopeBarItem(WI.AuditTestCaseResult.Level.Unsupported, WI.UIString("%d Unsupported", "%d Unsupported (singular)", ""), WI.UIString("%d Unsupported", "%d Unsupported (plural)", ""));
this._levelScopeBar = new WI.ScopeBar(null, scopeBarItems);
this._levelScopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._handleLevelScopeBarSelectionChanged, this);
this._levelNavigationBar.addNavigationItem(this._levelScopeBar);
}
if (this.applyFilter())
this.hidePlaceholder();
else
this.showFilteredPlaceholder();
}
attached()
{
super.attached();
if (this.representedObject instanceof WI.AuditTestGroup) {
this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
if (this.representedObject.editable) {
this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this);
}
}
console.assert(!this._viewForSubobject.size);
for (let subobject of this._subobjects())
this._addTest(subobject);
}
detached()
{
if (this.representedObject instanceof WI.AuditTestGroup) {
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
if (this.representedObject.editable) {
this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this);
}
}
this.contentView.removeAllSubviews();
this._viewForSubobject.clear();
super.detached();
}
applyFilter(levels)
{
if (this._levelScopeBar && !levels)
levels = this._levelScopeBar.selectedItems.map((item) => item.id);
this._updateLevelScopeBar(levels);
return super.applyFilter(levels);
}
resetFilter()
{
this._updateLevelScopeBar(Object.values(WI.AuditTestCaseResult.Level));
super.resetFilter();
}
showRunningPlaceholder()
{
if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) {
this.placeholderElement = WI.createMessageTextView(WI.UIString("Running the \u201C%s\u201D audit").format(this.representedObject.name));
this.placeholderElement.__placeholderRunning = true;
this.placeholderElement.__progress = document.createElement("progress");
this.placeholderElement.__progress.value = 0;
this.placeholderElement.appendChild(this.placeholderElement.__progress);
let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13);
stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, function(event) {
WI.auditManager.stop();
}, stopAuditNavigationItem);
let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem);
this.placeholderElement.appendChild(stopAuditHelpElement);
this.placeholderElement.appendChild(WI.ReferencePage.AuditTab.RunningAudits.createLinkElement());
}
super.showRunningPlaceholder();
}
// Private
_subobjects()
{
if (this.representedObject instanceof WI.AuditTestGroup)
return this.representedObject.tests;
if (this.representedObject instanceof WI.AuditTestGroupResult)
return this.representedObject.results;
console.error("Unknown representedObject", this.representedObject);
return [];
}
_updateClassList()
{
let subobjects = this._subobjects();
let containsTestGroup = subobjects.some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult);
this.element.classList.toggle("contains-test-group", containsTestGroup);
this.element.classList.toggle("contains-test-case", !containsTestGroup && subobjects.some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult));
}
_updateLevelScopeBar(levels)
{
if (!this._levelScopeBar)
return;
for (let item of this._levelScopeBar.items)
item.selected = levels.includes(item.id);
for (let view of this._viewForSubobject.values()) {
if (view instanceof WI.AuditTestGroupContentView)
view._updateLevelScopeBar(levels);
}
}
_addTest(test)
{
console.assert(!this._viewForSubobject.has(test));
let view = WI.ContentView.contentViewForRepresentedObject(test);
this.contentView.addSubview(view);
this._viewForSubobject.set(test, view);
}
_handleTestGroupProgress(event)
{
let {index, count} = event.data;
if (this.placeholderElement && this.placeholderElement.__progress)
this.placeholderElement.__progress.value = (index + 1) / count;
}
_handleTestGroupScheduled(event)
{
if (this.placeholderElement && this.placeholderElement.__progress)
this.placeholderElement.__progress.value = 0;
}
_handleTestGroupTestAdded(event)
{
console.assert(WI.auditManager.editing);
let {test} = event.data;
this._addTest(test);
this._updateClassList();
}
_handleTestGroupTestRemoved(event)
{
console.assert(WI.auditManager.editing);
let {test} = event.data;
let view = this._viewForSubobject.get(test);
console.assert(view);
this.contentView.removeSubview(view);
this._updateClassList();
}
_handleLevelScopeBarSelectionChanged(event)
{
this.needsLayout();
}
};
+130
View File
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2018-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.
*/
.tree-outline .item.audit > .status {
display: flex;
align-items: center;
width: 16px;
height: 16px;
}
.tree-outline .item.audit > .status > img {
width: 100%;
height: 100%;
}
.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img {
width: 75%;
height: 75%;
margin: 0 auto;
content: url(../Images/AuditStart.svg) !important;
}
body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active).selected:hover > .status > img,
body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-case.selected > .status > .indeterminate-progress-spinner,
body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-group.selected > .status > progress {
filter: var(--filter-invert);
}
.tree-outline .item.audit:not(:hover) > .status > img.show-on-hover,
.tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits):not(:hover) > .status {
opacity: 0;
}
.tree-outline .item.audit.manager-active > .status > img.show-on-hover,
.tree-outline .item.audit.test-group.expanded:not(.editing-audits):hover > .status > :not(img),
.tree-outline .item.audit.test-group-result.expanded > .status,
.tree-outline .item.audit.unsupported + .children .item.audit.unsupported > .status > img {
display: none;
}
.tree-outline .item.audit.unsupported:not(.selected) > :matches(.icon, .titles) {
opacity: 0.5;
}
.tree-outline .item.audit.unsupported > .status > img {
width: 75%;
height: 75%;
margin: 0 auto;
content: url(../Images/Warning.svg);
}
.tree-outline .item.audit > .status > img.pass {
content: url(../Images/AuditTestPass.svg);
}
.tree-outline .item.audit > .status > img.warn {
content: url(../Images/AuditTestWarn.svg);
}
.tree-outline .item.audit > .status > img.fail {
content: url(../Images/AuditTestFail.svg);
}
.tree-outline .item.audit > .status > img.error {
content: url(../Images/AuditTestError.svg);
}
.tree-outline .item.audit > .status > img.unsupported {
content: url(../Images/AuditTestUnsupported.svg);
}
.audit.test-case .icon {
content: url(../Images/TypeIcons.svg#AuditTestCase-light);
}
.audit.test-group .icon {
content: url(../Images/TypeIcons.svg#AuditTestGroup-light);
}
.audit.test-case-result .icon {
content: url(../Images/TypeIcons.svg#AuditTestCaseResult-light);
}
.audit.test-group-result .icon {
content: url(../Images/TypeIcons.svg#AuditTestGroupResult-light);
}
@media (prefers-color-scheme: dark) {
.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img {
filter: var(--filter-invert) brightness(90%);
}
.audit.test-case .icon {
content: url(../Images/TypeIcons.svg#AuditTestCase-dark);
}
.audit.test-group .icon {
content: url(../Images/TypeIcons.svg#AuditTestGroup-dark);
}
.audit.test-case-result .icon {
content: url(../Images/TypeIcons.svg#AuditTestCaseResult-dark);
}
.audit.test-group-result .icon {
content: url(../Images/TypeIcons.svg#AuditTestGroupResult-dark);
}
}
+434
View File
@@ -0,0 +1,434 @@
/*
* 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.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement
{
constructor(representedObject)
{
let isTestCase = representedObject instanceof WI.AuditTestCase;
let isTestGroup = representedObject instanceof WI.AuditTestGroup;
let isTestCaseResult = representedObject instanceof WI.AuditTestCaseResult;
let isTestGroupResult = representedObject instanceof WI.AuditTestGroupResult;
console.assert(isTestCase || isTestGroup || isTestCaseResult || isTestGroupResult);
let classNames = ["audit"];
if (isTestCase)
classNames.push("test-case");
else if (isTestGroup)
classNames.push("test-group");
else if (isTestCaseResult)
classNames.push("test-case-result");
else if (isTestGroupResult)
classNames.push("test-group-result");
let options = {
hasChildren: isTestGroup || isTestGroupResult,
};
const subtitle = null;
super(classNames, representedObject.name, subtitle, representedObject, options);
if (isTestGroup)
this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), false);
}
// Static
static expandedSettingKey(name)
{
return `audit-tree-element-${name}-expanded`;
}
// Protected
onattach()
{
super.onattach();
if (this.representedObject instanceof WI.AuditTestBase) {
this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this._handleTestResultChanged, this);
if (this.representedObject instanceof WI.AuditTestCase)
this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestCaseScheduled, this);
else if (this.representedObject instanceof WI.AuditTestGroup)
this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
if (this.representedObject.editable) {
this.representedObject.addEventListener(WI.AuditTestBase.Event.NameChanged, this._handleTestNameChanged, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
if (this.representedObject instanceof WI.AuditTestGroup)
this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
}
WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this);
WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this);
}
if (this.representedObject.supported && this._expandedSetting && this._expandedSetting.value)
this.expand();
this._updateStatus();
}
ondetach()
{
if (this.representedObject instanceof WI.AuditTestBase) {
this.representedObject.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.ResultChanged, this._handleTestResultChanged, this);
if (this.representedObject instanceof WI.AuditTestCase)
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestCaseScheduled, this);
else if (this.representedObject instanceof WI.AuditTestGroup)
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
if (this.representedObject.editable) {
this.representedObject.removeEventListener(WI.AuditTestBase.Event.NameChanged, this._handleTestNameChanged, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
if (this.representedObject instanceof WI.AuditTestGroup)
this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
}
WI.auditManager.removeEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this);
WI.auditManager.removeEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this);
}
super.ondetach();
}
onpopulate()
{
super.onpopulate();
if (this.children.length && !this.shouldRefreshChildren)
return;
this.shouldRefreshChildren = false;
this.removeChildren();
if (this.representedObject instanceof WI.AuditTestGroup) {
for (let test of this.representedObject.tests)
this.appendChild(new WI.AuditTreeElement(test));
} else if (this.representedObject instanceof WI.AuditTestGroupResult) {
for (let result of this.representedObject.results)
this.appendChild(new WI.AuditTreeElement(result));
}
}
onexpand()
{
console.assert(this.expanded);
if (this._expandedSetting)
this._expandedSetting.value = this.expanded;
}
oncollapse()
{
console.assert(!this.expanded);
if (this._expandedSetting)
this._expandedSetting.value = this.expanded;
}
ondelete()
{
if (!(this.representedObject instanceof WI.AuditTestBase))
return false;
if (!WI.auditManager.editing)
return false;
this.representedObject.remove();
return true;
}
canSelectOnMouseDown(event)
{
if (this.representedObject instanceof WI.AuditTestBase && this.representedObject.supported && this.status.contains(event.target))
return false;
return super.canSelectOnMouseDown(event);
}
populateContextMenu(contextMenu, event)
{
let isTest = this.representedObject instanceof WI.AuditTestBase;
contextMenu.appendSeparator();
if (WI.auditManager.editing) {
if (isTest) {
if (this.representedObject.supported) {
contextMenu.appendItem(this.representedObject.disabled ? WI.UIString("Enable Audit") : WI.UIString("Disable Audit"), () => {
this.representedObject.disabled = !this.representedObject.disabled;
});
}
contextMenu.appendItem(WI.UIString("Duplicate Audit"), async () => {
let audit = await this.representedObject.clone();
WI.auditManager.addTest(audit, {save: true});
});
if (this.representedObject.editable) {
contextMenu.appendItem(WI.UIString("Delete Audit"), () => {
this.representedObject.remove();
});
}
}
} else {
if (isTest) {
contextMenu.appendItem(WI.UIString("Start Audit"), () => {
this._start();
}, WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive);
}
contextMenu.appendSeparator();
if (WI.FileUtilities.canSave(WI.FileUtilities.SaveMode.FileVariants)) {
contextMenu.appendItem(WI.UIString("Export"), () => {
WI.auditManager.export(WI.FileUtilities.SaveMode.FileVariants, this.representedObject);
});
} else if (WI.FileUtilities.canSave(WI.FileUtilities.SaveMode.SingleFile)) {
if (isTest) {
contextMenu.appendItem(WI.FileUtilities.SaveMode.SingleFile, WI.UIString("Export Audit"), () => {
WI.auditManager.export(this.representedObject);
});
}
contextMenu.appendItem(WI.FileUtilities.SaveMode.SingleFile, WI.UIString("Export Result"), () => {
WI.auditManager.export(this.representedObject.result);
}, !this.representedObject.result);
}
if (isTest && this.representedObject.editable) {
contextMenu.appendSeparator();
contextMenu.appendItem(WI.UIString("Edit Audit"), () => {
WI.auditManager.editing = true;
WI.showRepresentedObject(this.representedObject);
});
}
}
contextMenu.appendSeparator();
super.populateContextMenu(contextMenu, event);
}
// Private
_start()
{
if (WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive)
return;
WI.auditManager.start([this.representedObject]);
}
_updateStatus()
{
if (this.representedObject instanceof WI.AuditTestBase && !this.representedObject.supported) {
this.status = document.createElement("img");
this.status.title = WI.UIString("This audit is not supported");
this.addClassName("unsupported");
return;
}
if (WI.auditManager.editing) {
this.status = document.createElement("input");
this.status.type = "checkbox";
this._updateTestGroupDisabled();
this.status.addEventListener("change", () => {
this.representedObject.disabled = !this.representedObject.disabled;
});
this.addClassName("editing-audits");
return;
}
let className = "";
let result = this.representedObject.result;
if (result) {
if (result.didError)
className = WI.AuditTestCaseResult.Level.Error;
else if (result.didFail)
className = WI.AuditTestCaseResult.Level.Fail;
else if (result.didWarn)
className = WI.AuditTestCaseResult.Level.Warn;
else if (result.didPass)
className = WI.AuditTestCaseResult.Level.Pass;
else if (result.unsupported)
className = WI.AuditTestCaseResult.Level.Unsupported;
}
this.status = document.createElement("img");
if (this.representedObject instanceof WI.AuditTestBase) {
this.status.title = WI.UIString("Start");
this.status.addEventListener("click", this._handleStatusClick.bind(this));
if (!className)
className = "show-on-hover";
}
this.status.classList.add(className);
this.removeClassName("editing-audits");
}
_showRunningSpinner()
{
if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) {
this._updateStatus();
return;
}
if (!this.status || !this.status.__spinner) {
let spinner = new WI.IndeterminateProgressSpinner;
this.status = spinner.element;
this.status.__spinner = true;
}
}
_showRunningProgress(progress)
{
if (!this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) {
this._updateStatus();
return;
}
if (!this.status || !this.status.__progress) {
this.status = document.createElement("progress");
this.status.__progress = true;
}
this.status.value = progress || 0;
}
_updateTestGroupDisabled()
{
this.status.checked = !this.representedObject.disabled;
if (this.representedObject instanceof WI.AuditTestGroup) {
let firstSupportedTest = this.representedObject.tests.find((test) => test.supported);
this.status.indeterminate = this.representedObject.tests.some((test) => test.supported && test.disabled !== firstSupportedTest.disabled);
}
}
_handleTestCaseCompleted(event)
{
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCaseCompleted, this);
this._updateStatus();
}
_handleTestDisabledChanged(event)
{
if (this.status instanceof HTMLInputElement && this.status.type === "checkbox")
this._updateTestGroupDisabled();
}
_handleTestResultChanged(event)
{
this._updateStatus();
}
_handleTestCaseScheduled(event)
{
this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCaseCompleted, this);
this._showRunningSpinner();
}
_handleTestGroupCompleted(event)
{
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestGroupCompleted, this);
this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
this._updateStatus();
}
_handleTestGroupProgress(event)
{
let {index, count} = event.data;
this._showRunningProgress((index + 1) / count);
}
_handleTestGroupScheduled(event)
{
this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestGroupCompleted, this);
this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
this._showRunningProgress();
}
_handleTestNameChanged(event)
{
this.mainTitle = this.representedObject.name;
if (this.representedObject instanceof WI.AuditTestGroup)
this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), !!WI.Setting.migrateValue(WI.AuditTreeElement.expandedSettingKey(event.data.oldName)));
}
_handleTestSupportedChanged(event)
{
this._updateStatus();
}
_handleTestGroupTestAdded(event)
{
let {test} = event.data;
this.appendChild(new WI.AuditTreeElement(test));
}
_handleManagerEditingChanged(event)
{
this._updateStatus();
}
_handleAuditManagerTestScheduled(event)
{
this.addClassName("manager-active");
}
_handleAuditManagerTestCompleted(event)
{
this.removeClassName("manager-active");
}
_handleStatusClick(event)
{
this._start();
}
};
+52
View File
@@ -0,0 +1,52 @@
/*
* 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.
*/
.banner-view {
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
position: relative;
height: var(--navigation-bar-height);
padding: 0 4px;
color: var(--yellow-highlight-text-color);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
background-color: var(--yellow-highlight-background-color);
border-bottom: 1px solid var(--border-color);
}
.banner-view > button {
margin-inline-start: 4px;
}
.banner-view > .dismiss {
position: absolute;
inset-inline-end: 5px;
width: 16px;
height: 16px;
padding-bottom: 2px;
}
+63
View File
@@ -0,0 +1,63 @@
/*
* 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.BannerView = class BannerView extends WI.View
{
constructor(message, {actionButtonMessage, showDismissButton} = {})
{
super();
this.element.classList.add("banner-view");
this.element.appendChild(document.createTextNode(message));
if (actionButtonMessage) {
let actionButtonElement = this.element.appendChild(document.createElement("button"));
actionButtonElement.textContent = actionButtonMessage;
actionButtonElement.addEventListener("click", this._handleActionButtonClicked.bind(this));
}
if (showDismissButton) {
let dismissButtonElement = this.element.appendChild(WI.ImageUtilities.useSVGSymbol("Images/Close.svg", "dismiss", WI.UIString("Dismiss", "Dismiss @ Banner View", "Tooltip for the dismiss button in banner views.")));
dismissButtonElement.addEventListener("click", this._handleDismissButtonClicked.bind(this));
}
}
// Private
_handleActionButtonClicked(event)
{
this.dispatchEventToListeners(WI.BannerView.Event.ActionButtonClicked);
}
_handleDismissButtonClicked(event)
{
this.dispatchEventToListeners(WI.BannerView.Event.DismissButtonClicked);
}
};
WI.BannerView.Event = {
ActionButtonClicked: "banner-view-action-button-clicked",
DismissButtonClicked: "banner-view-dismiss-button-clicked",
};
+114
View File
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2015-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.
*/
.bezier-editor {
width: 200px;
height: 258px;
--bezier-editor-preview-translateX: 170px;
}
.bezier-editor > .bezier-preview {
width: calc(100% - 10px);
height: 25px;
margin: 5px 5px 0;
border-bottom: 1px solid var(--border-color);
}
.bezier-editor > .bezier-preview > div {
width: 20px;
height: 20px;
background-color: var(--glyph-color-active);
border-radius: 50%;
}
.bezier-editor > .bezier-preview-timing {
position: absolute;
top: 34px;
margin-inline-start: 11px;
border-inline: 4px solid transparent;
border-top: 4px solid var(--text-color);
}
.bezier-editor > .bezier-preview.animate > div,
.bezier-editor > .bezier-preview-timing.animate {
animation: bezierPreview 2.5s linear 250ms infinite;
}
@keyframes bezierPreview {
from { transform: translateX(0); }
10% { transform: translateX(0); }
90% { transform: translateX(var(--bezier-editor-preview-translateX)); }
to { transform: translateX(var(--bezier-editor-preview-translateX)); }
}
.bezier-editor > .bezier-container {
margin: 0 8px;
}
.bezier-editor > .bezier-container .linear-curve {
fill: none;
stroke: var(--text-color-quaternary);
stroke-width: 2;
stroke-linecap: round;
}
.bezier-editor > .bezier-container .bezier-curve {
fill: none;
stroke: var(--text-color);
stroke-width: 2;
stroke-linecap: round;
}
.bezier-editor > .bezier-container .control-line {
fill: none;
stroke: var(--glyph-color-active);
stroke-width: 2;
stroke-linecap: round;
}
.bezier-editor > .bezier-container .control-handle {
r: 5px;
fill: var(--glyph-color-active);
}
.bezier-editor > .bezier-container .control-handle.selected {
r: 7px;
}
.bezier-editor > .number-input-container {
display: flex;
width: calc(100% - 10px);
margin: 0 5px;
padding-top: 7px;
border-top: 1px solid var(--border-color);
}
.bezier-editor > .number-input-container > input {
width: 100%;
margin: 0 2px;
padding: 1px 0 1px;
text-align: end;
}
+392
View File
@@ -0,0 +1,392 @@
/*
* 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.BezierEditor = class BezierEditor extends WI.Object
{
constructor()
{
super();
this._element = document.createElement("div");
this._element.classList.add("bezier-editor");
this._element.dir = "ltr";
var editorWidth = 184;
var editorHeight = 200;
this._padding = 25;
this._controlHandleRadius = 7;
this._bezierWidth = editorWidth - (this._controlHandleRadius * 2);
this._bezierHeight = editorHeight - (this._controlHandleRadius * 2) - (this._padding * 2);
this._bezierPreviewContainer = this._element.createChild("div", "bezier-preview");
this._bezierPreviewContainer.title = WI.UIString("Restart animation");
this._bezierPreviewContainer.addEventListener("mousedown", this._resetPreviewAnimation.bind(this));
this._bezierPreview = this._bezierPreviewContainer.createChild("div");
this._bezierPreviewTiming = this._element.createChild("div", "bezier-preview-timing");
this._bezierContainer = this._element.appendChild(createSVGElement("svg"));
this._bezierContainer.setAttribute("width", editorWidth);
this._bezierContainer.setAttribute("height", editorHeight);
this._bezierContainer.classList.add("bezier-container");
let svgGroup = this._bezierContainer.appendChild(createSVGElement("g"));
svgGroup.setAttribute("transform", "translate(0, " + this._padding + ")");
let linearCurve = svgGroup.appendChild(createSVGElement("line"));
linearCurve.classList.add("linear-curve");
linearCurve.setAttribute("x1", this._controlHandleRadius);
linearCurve.setAttribute("y1", this._bezierHeight + this._controlHandleRadius);
linearCurve.setAttribute("x2", this._bezierWidth + this._controlHandleRadius);
linearCurve.setAttribute("y2", this._controlHandleRadius);
this._bezierCurve = svgGroup.appendChild(createSVGElement("path"));
this._bezierCurve.classList.add("bezier-curve");
function createControl(x1, y1)
{
x1 += this._controlHandleRadius;
y1 += this._controlHandleRadius;
let line = svgGroup.appendChild(createSVGElement("line"));
line.classList.add("control-line");
line.setAttribute("x1", x1);
line.setAttribute("y1", y1);
line.setAttribute("x2", x1);
line.setAttribute("y2", y1);
let handle = svgGroup.appendChild(createSVGElement("circle"));
handle.classList.add("control-handle");
return {point: null, line, handle};
}
this._inControl = createControl.call(this, 0, this._bezierHeight);
this._outControl = createControl.call(this, this._bezierWidth, 0);
this._numberInputContainer = this._element.createChild("div", "number-input-container");
function createBezierInput(id, {min, max} = {})
{
let key = "_bezier" + id + "Input";
this[key] = this._numberInputContainer.createChild("input");
this[key].type = "number";
this[key].step = 0.01;
if (!isNaN(min))
this[key].min = min;
if (!isNaN(max))
this[key].max = max;
this[key].addEventListener("input", this._handleNumberInputInput.bind(this));
this[key].addEventListener("keydown", this._handleNumberInputKeydown.bind(this));
}
createBezierInput.call(this, "InX", {min: 0, max: 1});
createBezierInput.call(this, "InY");
createBezierInput.call(this, "OutX", {min: 0, max: 1});
createBezierInput.call(this, "OutY");
this._selectedControl = null;
this._mouseDownPosition = null;
this._bezierContainer.addEventListener("mousedown", this);
WI.addWindowKeydownListener(this);
}
// Public
get element()
{
return this._element;
}
set bezier(bezier)
{
if (!bezier)
return;
var isCubicBezier = bezier instanceof WI.CubicBezier;
console.assert(isCubicBezier);
if (!isCubicBezier)
return;
this._bezier = bezier;
this._updateBezierPreview();
}
get bezier()
{
return this._bezier;
}
removeListeners()
{
WI.removeWindowKeydownListener(this);
}
// Protected
handleEvent(event)
{
switch (event.type) {
case "mousedown":
this._handleMousedown(event);
break;
case "mousemove":
this._handleMousemove(event);
break;
case "mouseup":
this._handleMouseup(event);
break;
}
}
handleKeydownEvent(event)
{
if (!this._selectedControl || !this._element.parentNode)
return false;
let horizontal = 0;
let vertical = 0;
switch (event.keyCode) {
case WI.KeyboardShortcut.Key.Up.keyCode:
vertical = -1;
break;
case WI.KeyboardShortcut.Key.Right.keyCode:
horizontal = 1;
break;
case WI.KeyboardShortcut.Key.Down.keyCode:
vertical = 1;
break;
case WI.KeyboardShortcut.Key.Left.keyCode:
horizontal = -1;
break;
default:
return false;
}
if (event.shiftKey) {
horizontal *= 10;
vertical *= 10;
}
vertical *= this._bezierWidth / 100;
horizontal *= this._bezierHeight / 100;
this._selectedControl.point.x = Number.constrain(this._selectedControl.point.x + horizontal, 0, this._bezierWidth);
this._selectedControl.point.y += vertical;
this._updateControl(this._selectedControl);
this._updateValue();
return true;
}
// Private
_handleMousedown(event)
{
if (event.button !== 0)
return;
event.stop();
window.addEventListener("mousemove", this, true);
window.addEventListener("mouseup", this, true);
this._bezierPreviewContainer.classList.remove("animate");
this._bezierPreviewTiming.classList.remove("animate");
this._updateControlPointsForMouseEvent(event, true);
}
_handleMousemove(event)
{
this._updateControlPointsForMouseEvent(event);
}
_handleMouseup(event)
{
this._selectedControl.handle.classList.remove("selected");
this._mouseDownPosition = null;
this._triggerPreviewAnimation();
window.removeEventListener("mousemove", this, true);
window.removeEventListener("mouseup", this, true);
}
_updateControlPointsForMouseEvent(event, calculateSelectedControlPoint)
{
var point = WI.Point.fromEventInElement(event, this._bezierContainer);
point.x = Number.constrain(point.x - this._controlHandleRadius, 0, this._bezierWidth);
point.y -= this._controlHandleRadius + this._padding;
if (calculateSelectedControlPoint) {
this._mouseDownPosition = point;
if (this._inControl.point.distance(point) < this._outControl.point.distance(point))
this._selectedControl = this._inControl;
else
this._selectedControl = this._outControl;
}
if (event.shiftKey && this._mouseDownPosition) {
if (Math.abs(this._mouseDownPosition.x - point.x) > Math.abs(this._mouseDownPosition.y - point.y))
point.y = this._mouseDownPosition.y;
else
point.x = this._mouseDownPosition.x;
}
this._selectedControl.point = point;
this._selectedControl.handle.classList.add("selected");
this._updateValue();
}
_updateValue()
{
function round(num)
{
return Math.round(num * 100) / 100;
}
var inValueX = round(this._inControl.point.x / this._bezierWidth);
var inValueY = round(1 - (this._inControl.point.y / this._bezierHeight));
var outValueX = round(this._outControl.point.x / this._bezierWidth);
var outValueY = round(1 - (this._outControl.point.y / this._bezierHeight));
this._bezier = new WI.CubicBezier(inValueX, inValueY, outValueX, outValueY);
this._updateBezier();
this.dispatchEventToListeners(WI.BezierEditor.Event.BezierChanged, {bezier: this._bezier});
}
_updateBezier()
{
var r = this._controlHandleRadius;
var inControlX = this._inControl.point.x + r;
var inControlY = this._inControl.point.y + r;
var outControlX = this._outControl.point.x + r;
var outControlY = this._outControl.point.y + r;
var path = `M ${r} ${this._bezierHeight + r} C ${inControlX} ${inControlY} ${outControlX} ${outControlY} ${this._bezierWidth + r} ${r}`;
this._bezierCurve.setAttribute("d", path);
this._updateControl(this._inControl);
this._updateControl(this._outControl);
this._bezierInXInput.value = this._bezier.inPoint.x;
this._bezierInYInput.value = this._bezier.inPoint.y;
this._bezierOutXInput.value = this._bezier.outPoint.x;
this._bezierOutYInput.value = this._bezier.outPoint.y;
}
_updateControl(control)
{
control.handle.setAttribute("cx", control.point.x + this._controlHandleRadius);
control.handle.setAttribute("cy", control.point.y + this._controlHandleRadius);
control.line.setAttribute("x2", control.point.x + this._controlHandleRadius);
control.line.setAttribute("y2", control.point.y + this._controlHandleRadius);
}
_updateBezierPreview()
{
this._inControl.point = new WI.Point(this._bezier.inPoint.x * this._bezierWidth, (1 - this._bezier.inPoint.y) * this._bezierHeight);
this._outControl.point = new WI.Point(this._bezier.outPoint.x * this._bezierWidth, (1 - this._bezier.outPoint.y) * this._bezierHeight);
this._updateBezier();
this._triggerPreviewAnimation();
}
_triggerPreviewAnimation()
{
this._bezierPreview.style.animationTimingFunction = this._bezier.toString();
this._bezierPreviewContainer.classList.add("animate");
this._bezierPreviewTiming.classList.add("animate");
}
_resetPreviewAnimation()
{
var parent = this._bezierPreview.parentNode;
parent.removeChild(this._bezierPreview);
parent.appendChild(this._bezierPreview);
this._element.removeChild(this._bezierPreviewTiming);
this._element.appendChild(this._bezierPreviewTiming);
}
_handleNumberInputInput(event)
{
this._changeBezierForInput(event.target, event.target.value);
}
_handleNumberInputKeydown(event)
{
let shift = 0;
if (event.keyIdentifier === "Up")
shift = 0.01;
else if (event.keyIdentifier === "Down")
shift = -0.01;
if (!shift)
return;
if (event.shiftKey)
shift *= 10;
event.preventDefault();
this._changeBezierForInput(event.target, parseFloat(event.target.value) + shift);
}
_changeBezierForInput(target, value)
{
value = Math.round(value * 100) / 100;
switch (target) {
case this._bezierInXInput:
this._bezier.inPoint.x = Number.constrain(value, 0, 1);
break;
case this._bezierInYInput:
this._bezier.inPoint.y = value;
break;
case this._bezierOutXInput:
this._bezier.outPoint.x = Number.constrain(value, 0, 1);
break;
case this._bezierOutYInput:
this._bezier.outPoint.y = value;
break;
default:
return;
}
this._updateBezierPreview();
this.dispatchEventToListeners(WI.BezierEditor.Event.BezierChanged, {bezier: this._bezier});
}
};
WI.BezierEditor.Event = {
BezierChanged: "bezier-editor-bezier-changed"
};
+114
View File
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2019-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.
*/
.settings-view.blackbox > :matches(p, table) {
width: 100%;
max-width: calc(min(50em, 100% - 40px));
margin: 1em auto 2em;
}
.settings-view.blackbox > p {
font-size: 12px;
}
.settings-view.blackbox > * + p {
margin-top: 2em;
}
.settings-view.blackbox > p:last-child {
margin-bottom: 0;
}
.settings-view.blackbox > p > .toggle-script-blackbox {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
}
.settings-view.blackbox > p > label {
display: flex;
}
.settings-view.blackbox > p > label > input {
flex-shrink: 0;
margin-inline: 0.5px 4px;
}
.settings-view.blackbox > p > label > input[type="checkbox"] {
width: 15px;
height: 15px;
margin-top: 1px;
}
.settings-view.blackbox > p > label > span {
margin-top: 1.5px;
}
.settings-view.blackbox > table {
border-collapse: collapse;
}
.settings-view.blackbox > table > thead th {
padding: 0 4px 4px;
}
.settings-view.blackbox > table > tbody td {
padding: 2px 0;
}
.settings-view.blackbox > table > tbody td:not(.remove-blackbox) {
border: 1px solid var(--border-color);
}
.settings-view.blackbox > table :matches(th, td).url {
text-align: start;
}
.settings-view.blackbox > table > tbody td.url {
background-color: var(--background-color-content);
}
.settings-view.blackbox > table > tbody td.url > .CodeMirror {
min-width: 110px;
height: auto;
background-color: transparent;
}
.settings-view.blackbox > table :matches(th, td):matches(.case-sensitive, .remove-blackbox) {
width: 1px;
white-space: nowrap;
text-align: center;
}
.settings-view.blackbox > table > tbody > tr:not(:hover) > td.remove-blackbox {
opacity: 0;
}
.settings-view.blackbox > table > tbody td.remove-blackbox > .remove-blackbox-button {
width: 15px;
height: 15px;
margin-inline-start: 4px;
}
+200
View File
@@ -0,0 +1,200 @@
/*
* 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.BlackboxSettingsView = class BlackboxSettingsView extends WI.SettingsView
{
constructor()
{
super("blackbox", WI.UIString("Blackbox"));
this._blackboxPatternCodeMirrorMap = new Map;
}
// Public
selectBlackboxPattern(regex)
{
console.assert(regex instanceof RegExp);
console.assert(this.didInitialLayout);
let codeMirror = this._blackboxPatternCodeMirrorMap.get(regex);
console.assert(codeMirror);
if (!codeMirror)
return;
codeMirror.focus();
}
// Protected
initialLayout()
{
super.initialLayout();
let patternBlackboxExplanationElement = this.element.insertBefore(document.createElement("p"), this.element.lastChild);
patternBlackboxExplanationElement.textContent = WI.UIString("If the URL of any script matches one of the regular expression patterns below, any pauses that would have happened in that script will be deferred until execution has continued to outside of that script.");
let table = this.element.insertBefore(document.createElement("table"), this.element.lastChild);
let tableHead = table.appendChild(document.createElement("thead"));
let tableHeadRow = tableHead.appendChild(document.createElement("tr"));
let urlHeaderCell = tableHeadRow.appendChild(document.createElement("th"));
urlHeaderCell.classList.add("url");
urlHeaderCell.textContent = WI.UIString("URL Pattern");
let caseSensitiveHeaderCell = tableHeadRow.appendChild(document.createElement("th"));
caseSensitiveHeaderCell.classList.add("case-sensitive");
caseSensitiveHeaderCell.textContent = WI.UIString("Case Sensitive");
let removeBlackboxHeaderCell = tableHeadRow.appendChild(document.createElement("th"));
removeBlackboxHeaderCell.classList.add("remove-blackbox");
this._tableBody = table.appendChild(document.createElement("tbody"));
for (let regex of WI.debuggerManager.blackboxPatterns)
this._addRow(regex);
if (!this._tableBody.children.length)
this._addRow(null);
let tableFoot = table.appendChild(document.createElement("tfoot"));
let tableFooterRow = tableFoot.appendChild(document.createElement("tr"));
let addBlackboxCell = tableFooterRow.appendChild(document.createElement("td"));
let addBlackboxButton = addBlackboxCell.appendChild(document.createElement("button"));
addBlackboxButton.textContent = WI.UIString("Add Pattern");
addBlackboxButton.addEventListener("click", (event) => {
for (let [regex, codeMirror] of this._blackboxPatternCodeMirrorMap) {
if (!regex) {
codeMirror.focus();
return;
}
}
this._addRow(null);
});
let individualBlackboxExplanationElement = this.element.insertBefore(document.createElement("p"), this.element.lastChild);
let blackboxIconElement = WI.ImageUtilities.useSVGSymbol("Images/Hide.svg#currentColor", "toggle-script-blackbox", WI.UIString("Ignore script when debugging"));
String.format(WI.UIString("Scripts can also be individually blackboxed by clicking on the %s icon that is shown on hover."), [blackboxIconElement], String.standardFormatters, individualBlackboxExplanationElement, (a, b) => {
a.append(b);
return a;
});
if (WI.DebuggerManager.supportsBlackboxingBreakpointEvaluations()) {
let blackboxBreakpointEvaluationsExplanationElement = this.element.insertBefore(document.createElement("p"), this.element.lastChild);
let blackboxBreakpointEvaluationsExplanationLabel = blackboxBreakpointEvaluationsExplanationElement.appendChild(document.createElement("label"));
let blackboxBreakpointEvaluationsExplanationCheckbox = blackboxBreakpointEvaluationsExplanationLabel.appendChild(document.createElement("input"));
blackboxBreakpointEvaluationsExplanationCheckbox.type = "checkbox";
blackboxBreakpointEvaluationsExplanationCheckbox.checked = WI.settings.blackboxBreakpointEvaluations.value;
blackboxBreakpointEvaluationsExplanationCheckbox.addEventListener("change", (event) => {
WI.settings.blackboxBreakpointEvaluations.value = blackboxBreakpointEvaluationsExplanationCheckbox.checked;
});
blackboxBreakpointEvaluationsExplanationLabel.append(WI.UIString("Also defer evaluating breakpoint conditions, ignore counts, and actions until execution has continued outside of the related script instead of at the breakpoint\u2019s location."));
}
}
// Private
_addRow(regex)
{
let tableBodyRow = this._tableBody.appendChild(document.createElement("tr"));
let urlBodyCell = tableBodyRow.appendChild(document.createElement("td"));
urlBodyCell.classList.add("url");
let urlCodeMirror = WI.CodeMirrorEditor.create(urlBodyCell, {
extraKeys: {"Tab": false, "Shift-Tab": false},
lineWrapping: false,
matchBrackets: false,
mode: "text/x-regex",
placeholder: WI.UIString("Regular Expression"),
scrollbarStyle: null,
value: regex ? regex.source : "",
});
this._blackboxPatternCodeMirrorMap.set(regex, urlCodeMirror);
this.needsLayout();
let caseSensitiveBodyCell = tableBodyRow.appendChild(document.createElement("td"));
caseSensitiveBodyCell.classList.add("case-sensitive");
let caseSensitiveCheckbox = caseSensitiveBodyCell.appendChild(document.createElement("input"));
caseSensitiveCheckbox.type = "checkbox";
caseSensitiveCheckbox.checked = regex ? !regex.ignoreCase : true;
let removeBlackboxBodyCell = tableBodyRow.appendChild(document.createElement("td"));
removeBlackboxBodyCell.classList.add("remove-blackbox");
let removeBlackboxButton = removeBlackboxBodyCell.appendChild(WI.ImageUtilities.useSVGSymbol("Images/NavigationItemTrash.svg", "remove-blackbox-button", WI.UIString("Delete Blackbox")));
removeBlackboxButton.addEventListener("click", (event) => {
if (regex)
WI.debuggerManager.setShouldBlackboxPattern(regex, false);
regex = null;
this._blackboxPatternCodeMirrorMap.delete(regex);
tableBodyRow.remove();
if (!this._tableBody.children.length)
this._addRow(null);
});
let update = () => {
let url = urlCodeMirror.getValue();
if (regex) {
if (regex.source === url && regex.ignoreCase !== caseSensitiveCheckbox.checked)
return;
WI.debuggerManager.setShouldBlackboxPattern(regex, false);
}
this._blackboxPatternCodeMirrorMap.delete(regex);
regex = url ? new RegExp(url, !caseSensitiveCheckbox.checked ? "i" : "") : null;
if (regex)
WI.debuggerManager.setShouldBlackboxPattern(regex, true);
console.assert(regex || !this._blackboxPatternCodeMirrorMap.has(regex));
this._blackboxPatternCodeMirrorMap.set(regex, urlCodeMirror);
};
urlCodeMirror.addKeyMap({
"Enter": update,
"Esc": update,
});
urlCodeMirror.on("blur", update);
caseSensitiveCheckbox.addEventListener("change", update);
if (!regex)
urlCodeMirror.focus();
}
};
@@ -0,0 +1,39 @@
/*
* 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.
*/
/* Styles shared with `WI.BlackboxedGroupView`. */
.tree-outline .item.blackboxed-group.selected > * {
opacity: unset;
}
.tree-outline:not(:focus-within) .item.blackboxed-group.selected > *,
body:is(.window-inactive, .window-docked-inactive) .tree-outline .item.blackboxed-group.selected > * {
opacity: var(--blackboxed-opacity);
}
body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.blackboxed-group.selected .icon {
content: url(../Images/Hide.svg#white);
}
@@ -0,0 +1,80 @@
/*
* 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.BlackboxedGroupTreeElement = class BlackboxedGroupTreeElement extends WI.GeneralTreeElement
{
constructor(callFrames, {rememberBlackboxedCallFrameGroupToAutoExpand} = {})
{
console.assert(!rememberBlackboxedCallFrameGroupToAutoExpand || !WI.debuggerManager.shouldAutoExpandBlackboxedCallFrameGroup(callFrames), callFrames);
const classNames = ["blackboxed-group"];
super(classNames, WI.BlackboxedGroupView.generateTitle(callFrames), WI.BlackboxedGroupView.generateSubtitle(callFrames));
this.toggleOnClick = true;
this._callFrames = callFrames;
this._rememberBlackboxedCallFrameGroupToAutoExpand = rememberBlackboxedCallFrameGroupToAutoExpand || false;
}
// Public
get callFrames() { return this._callFrames; }
get expandable()
{
return true;
}
expand()
{
if (this._rememberBlackboxedCallFrameGroupToAutoExpand)
WI.debuggerManager.rememberBlackboxedCallFrameGroupToAutoExpand(this._callFrames);
let index = this.parent.children.indexOf(this);
for (let i = this._callFrames.length - 1; i >= 0; --i)
this.parent.insertChild(new WI.CallFrameTreeElement(this._callFrames[i]), index);
this.parent.removeChild(this);
}
onenter()
{
this.expand();
return true;
}
onspace()
{
this.expand();
return true;
}
// Protected
customTitleTooltip()
{
return WI.BlackboxedGroupView.generateTooltip(this._callFrames);
}
};
+70
View File
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 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.
*/
/* Styles shared with `WI.BlackboxedGroupTreeElement`. */
.blackboxed-group {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.blackboxed-group > * {
opacity: var(--blackboxed-opacity);
}
.blackboxed-group .icon {
display: inline-block;
width: 16px;
height: 16px;
margin-inline-end: 3px;
vertical-align: top;
content: url(../Images/Hide.svg#black);
}
.blackboxed-group .subtitle {
color: hsla(0, 0%, 0%, 0.6);
text-decoration: none;
}
.blackboxed-group .separator {
white-space: nowrap;
color: hsla(0, 0%, 0%, 0.2);
}
@media (prefers-color-scheme: dark) {
.blackboxed-group .icon {
content: url(../Images/Hide.svg#white);
}
.blackboxed-group .subtitle {
color: hsla(0, 0%, var(--foreground-lightness), 0.6);
}
.blackboxed-group .separator {
color: hsla(0, 0%, var(--foreground-lightness), 0.2);
}
}
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2022 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.BlackboxedGroupView = class BlackboxedGroupView
{
constructor(callFrames)
{
console.assert(Array.isArray(callFrames) && callFrames.length && callFrames.every((callFrame) => callFrame instanceof WI.CallFrame), callFrames);
let element = document.createElement("div");
element.className = "blackboxed-group";
element.title = WI.BlackboxedGroupView.generateTooltip(callFrames);
element.addEventListener("click", (event) => {
const options = {showFunctionName: true, indicateIfBlackboxed: true};
for (let i = callFrames.length - 1; i >= 0; --i)
element.parentElement.insertBefore(new WI.CallFrameView(callFrames[i], options), element);
element.remove();
});
let titleElement = element.appendChild(document.createElement("span"));
titleElement.className = "title";
let iconElement = titleElement.appendChild(document.createElement("img"));
iconElement.className = "icon";
titleElement.append(WI.BlackboxedGroupView.generateTitle(callFrames));
let subtitleElement = element.appendChild(document.createElement("span"));
subtitleElement.className = "subtitle";
let separatorElement = subtitleElement.appendChild(document.createElement("span"));
separatorElement.className = "separator";
separatorElement.textContent = " " + emDash + " ";
subtitleElement.append(WI.BlackboxedGroupView.generateSubtitle(callFrames));
return element;
}
// Static
static generateTitle(callFrames)
{
return WI.UIString("Blackboxed", "Blackboxed @ Debugger Call Stack", "Part of the 'Blackboxed - %d call frames' label shown in the debugger call stack when paused instead of subsequent call frames that have been blackboxed.");
}
static generateSubtitle(callFrames)
{
if (callFrames.length === 1)
return WI.UIString("%d call frame", "call frame @ Debugger Call Stack", "Part of the 'Blackboxed - %d call frame' label shown in the debugger call stack when paused instead of subsequent call frames that have been blackboxed.").format(callFrames.length);
return WI.UIString("%d call frames", "call frames @ Debugger Call Stack", "Part of the 'Blackboxed - %d call frames' label shown in the debugger call stack when paused instead of subsequent call frames that have been blackboxed.").format(callFrames.length);
}
static generateTooltip(callFrames)
{
if (callFrames.length === 1)
return WI.UIString("Click to show %d blackboxed call frame", "Click to show blackboxed call frame @ Debugger Call Stack", "Tooltip for the 'Blackboxed - %d call frame' label shown in the debugger call stack when paused instead of subsequent call frames that have been blackboxed.").format(callFrames.length);
return WI.UIString("Click to show %d blackboxed call frames", "Click to show blackboxed call frames @ Debugger Call Stack", "Tooltip for the 'Blackboxed - %d call frames' label shown in the debugger call stack when paused instead of subsequent call frames that have been blackboxed.").format(callFrames.length);
}
};
@@ -0,0 +1,28 @@
/*
* 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.
*/
.item.script.bootstrap .status > input[type="checkbox"] {
margin-top: 2px;
}
@@ -0,0 +1,107 @@
/*
* 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.BootstrapScriptTreeElement = class BootstrapScriptTreeElement extends WI.ScriptTreeElement
{
constructor(bootstrapScript)
{
console.assert(bootstrapScript === WI.networkManager.bootstrapScript);
super(bootstrapScript);
this.addClassName("bootstrap");
}
// Protected
onattach()
{
super.onattach();
WI.NetworkManager.addEventListener(WI.NetworkManager.Event.BootstrapScriptEnabledChanged, this._handleNetworkManagerBootstrapScriptEnabledChanged, this);
this.status = document.createElement("input");
this.status.type = "checkbox";
this.status.checked = WI.networkManager.bootstrapScriptEnabled;
this.status.addEventListener("change", (event) => {
WI.networkManager.bootstrapScriptEnabled = event.target.checked;
});
}
ondetach()
{
WI.NetworkManager.removeEventListener(WI.NetworkManager.Event.BootstrapScriptEnabledChanged, this._handleNetworkManagerBootstrapScriptEnabledChanged, this);
super.ondetach();
}
ondelete()
{
WI.networkManager.destroyBootstrapScript();
return true;
}
onspace()
{
WI.networkManager.bootstrapScriptEnabled = !WI.networkManager.bootstrapScriptEnabled;
return true;
}
canSelectOnMouseDown(event)
{
if (this.status.contains(event.target))
return false;
return super.canSelectOnMouseDown(event);
}
populateContextMenu(contextMenu, event)
{
let toggleEnabledString = WI.networkManager.bootstrapScriptEnabled ? WI.UIString("Disable Inspector Bootstrap Script") : WI.UIString("Enable Inspector Bootstrap Script");
contextMenu.appendItem(toggleEnabledString, () => {
WI.networkManager.bootstrapScriptEnabled = !WI.networkManager.bootstrapScriptEnabled;
});
contextMenu.appendItem(WI.UIString("Delete Inspector Bootstrap Script"), () => {
WI.networkManager.destroyBootstrapScript();
});
super.populateContextMenu(contextMenu, event);
}
updateStatus()
{
// Do nothing. Do not allow ScriptTreeElement / SourceCodeTreeElement to modify our status element.
}
// Private
_handleNetworkManagerBootstrapScriptEnabledChanged(event)
{
this.status.checked = WI.networkManager.bootstrapScriptEnabled;
}
};
@@ -0,0 +1,192 @@
/*
* Copyright (C) 2013-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.
*/
.details-section .row.box-model {
padding-bottom: 6px;
text-align: center;
white-space: nowrap;
}
.details-section .row.box-model .box {
display: inline-block;
position: relative;
margin: 3px; /* Keep this in sync with `WI.BoxModelDetailsSectionRow.prototype.get minimumWidth`. */
padding: 3px;
text-align: center;
vertical-align: middle;
background-color: white;
border: 1px solid grey;
}
.details-section .row.box-model .box > .label {
position: absolute;
padding: 0 2px;
}
.details-section .row.box-model .box.position {
border-style: dotted;
}
.details-section .row.box-model .box.margin {
border-style: dashed;
border-color: black;
}
.details-section .row.box-model:not(.hovered) .box.margin,
.details-section .row.box-model .box.margin.active {
background-color: hsla(30, 88%, 69%, 0.66);
}
.details-section .row.box-model .box.border {
border-color: black;
--has-border-radius-border-radius: 10px;
}
.details-section .row.box-model:not(.hovered) .box.border,
.details-section .row.box-model .box.border.active {
background-color: hsla(44, 100%, 80%, 0.66);
}
.details-section .row.box-model .box.border > .label {
margin-inline-start: 20px;
}
.details-section .row.box-model .box.border.has-top-left-radius,
.details-section .row.box-model .box.border.has-top-left-radius .box {
border-top-left-radius: var(--has-border-radius-border-radius);
}
.details-section .row.box-model .box.border.has-top-right-radius,
.details-section .row.box-model .box.border.has-top-right-radius .box {
border-top-right-radius: var(--has-border-radius-border-radius);
}
.details-section .row.box-model .box.border.has-bottom-right-radius,
.details-section .row.box-model .box.border.has-bottom-right-radius .box {
border-bottom-right-radius: var(--has-border-radius-border-radius);
}
.details-section .row.box-model .box.border.has-bottom-left-radius,
.details-section .row.box-model .box.border.has-bottom-left-radius .box {
border-bottom-left-radius: var(--has-border-radius-border-radius);
}
.details-section .row.box-model .box.padding {
border-style: dashed;
}
.details-section .row.box-model:not(.hovered) .box.padding,
.details-section .row.box-model .box.padding.active {
background-color: hsla(101, 37%, 62%, 0.55);
}
.details-section .row.box-model .box.content {
position: static;
min-width: 80px;
overflow: visible;
}
.details-section .row.box-model:not(.hovered) .box.content,
.details-section .row.box-model .box.content.active {
background-color: hsla(208, 60%, 64%, 0.66);
}
.details-section .row.box-model .editing {
position: relative;
z-index: 2;
}
.details-section .row.box-model :matches(.top, .right, .bottom, .left) {
display: inline-block;
vertical-align: middle;
}
.details-section .row.box-model :matches(.top, .right, .bottom, .left):not(.editing),
.details-section .row.box-model :matches(.top-left, .top-right, .bottom-right, .bottom-left) {
margin: 0 0.25em;
}
.details-section .row.box-model :matches(.top-left, .top-right, .bottom-right, .bottom-left) {
position: absolute;
}
.details-section .row.box-model :matches(.top-left, .top-right) {
top: 4px;
}
.details-section .row.box-model :matches(.bottom-left, .bottom-right):not(.editing) {
bottom: 3px;
}
.details-section .row.box-model :matches(.bottom-left, .bottom-right).editing {
bottom: 2px;
}
.details-section .row.box-model :matches(.top-left, .bottom-left):not(.editing) {
left: 3px;
}
.details-section .row.box-model :matches(.top-left, .bottom-left).editing {
left: 1px;
}
.details-section .row.box-model :matches(.top-right, .bottom-right):not(.editing) {
right: 3px;
}
.details-section .row.box-model :matches(.top-right, .bottom-right).editing {
right: 1px;
}
@media (prefers-color-scheme: dark) {
.details-section .row.box-model {
color: var(--text-color);
}
.details-section .row.box-model .label {
color: inherit;
}
.details-section .row.box-model .box {
border-color: var(--text-color-quaternary);
background-color: var(--background-color);
color: var(--text-color);
}
.details-section .row.box-model:not(.hovered) .box:matches(.margin, .border, .padding, .content),
.details-section .row.box-model .box.active:matches(.margin, .border, .padding, .content) {
color: black;
}
.details-section .row.box-model .box.margin {
border-color: var(--text-color-quaternary);
}
.details-section .row.box-model .box.border {
border-color: var(--text-color-quaternary);
}
}
@@ -0,0 +1,534 @@
/*
* Copyright (C) 2013, 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.BoxModelDetailsSectionRow = class BoxModelDetailsSectionRow extends WI.DetailsSectionRow
{
constructor()
{
super(WI.UIString("No Box Model Information"));
this.element.classList.add("box-model");
this._nodeStyles = null;
this._outermostBox = null;
this._outermostBoxWidth = NaN;
}
// Public
get nodeStyles()
{
return this._nodeStyles;
}
set nodeStyles(nodeStyles)
{
if (this._nodeStyles && this._nodeStyles.computedStyle)
this._nodeStyles.computedStyle.removeEventListener(WI.CSSStyleDeclaration.Event.PropertiesChanged, this._refresh, this);
this._nodeStyles = nodeStyles;
if (this._nodeStyles && this._nodeStyles.computedStyle)
this._nodeStyles.computedStyle.addEventListener(WI.CSSStyleDeclaration.Event.PropertiesChanged, this._refresh, this);
this.element.classList.remove("hovered");
this._refresh();
}
get minimumWidth()
{
if (isNaN(this._outermostBoxWidth) && this._outermostBox) {
const margin = 6; // Keep this in sync with `.details-section .row.box-model .box`.
this._outermostBoxWidth = this._outermostBox.realOffsetWidth + margin;
}
return this._outermostBoxWidth || 0;
}
// Private
_refresh()
{
if (this._ignoreNextRefresh) {
this._ignoreNextRefresh = false;
return;
}
this._updateMetrics();
}
_getPropertyValue(style, propertyName)
{
let property = style.propertyForName(propertyName);
if (!property)
return null;
return property.value;
}
_getPropertyValueAsPx(style, propertyName)
{
let value = this._getPropertyValue(style, propertyName);
if (!value)
return 0;
return Number(value.replace(/px$/, "") || 0);
}
_getBox(computedStyle, componentName)
{
let prefix = this._getComponentPrefix(componentName);
let suffix = this._getComponentSuffix(componentName);
let left = this._getPropertyValueAsPx(computedStyle, prefix + "-left" + suffix);
let top = this._getPropertyValueAsPx(computedStyle, prefix + "-top" + suffix);
let right = this._getPropertyValueAsPx(computedStyle, prefix + "-right" + suffix);
let bottom = this._getPropertyValueAsPx(computedStyle, prefix + "-bottom" + suffix);
return {left, top, right, bottom};
}
_getComponentPrefix(componentName)
{
return componentName === "border-radius" ? "border" : componentName;
}
_getComponentSuffix(componentName)
{
switch (componentName) {
case "border":
return "-width";
case "border-radius":
return "-radius";
}
return "";
}
_highlightDOMNode(showHighlight, mode, event)
{
event.stopPropagation();
let node = showHighlight ? this.nodeStyles.node : null;
if (node) {
if (this._highlightMode === mode)
return;
this._highlightMode = mode;
node.highlight(this._highlightMode);
} else {
this._highlightMode = null;
WI.domManager.hideDOMNodeHighlight();
}
for (var i = 0; this._boxElements && i < this._boxElements.length; ++i) {
var element = this._boxElements[i];
if (node && (mode === "all" || element._name === mode))
element.classList.add("active");
else
element.classList.remove("active");
}
this.element.classList.toggle("hovered", showHighlight);
}
_updateMetrics()
{
var metricsElement = document.createElement("div");
var style = this._nodeStyles.computedStyle;
function createValueElement(type, value, name, propertyName)
{
// Check if the value is a float and whether it should be rounded.
let floatValue = parseFloat(value);
let shouldRoundValue = !isNaN(floatValue) && (floatValue % 1 !== 0);
if (isNaN(floatValue))
value = figureDash;
let element = document.createElement(type);
element.textContent = shouldRoundValue ? ("~" + Math.round(floatValue * 100) / 100) : value;
if (shouldRoundValue)
element.title = value;
element.addEventListener("dblclick", this._startEditing.bind(this, element, name, propertyName, style), false);
return element;
}
function createBoxPartElement(name, side)
{
let prefix = this._getComponentPrefix(name);
let suffix = this._getComponentSuffix(name);
let propertyName = (prefix !== "position" ? prefix + "-" : "") + side + suffix;
let value = this._getPropertyValue(style, propertyName);
if (value) {
if (prefix === "position" && value === "auto")
value = "";
else if (prefix !== "position" && value === "0px")
value = "";
else
value = value.replace(/px$/, "");
} else
value = "";
let element = createValueElement.call(this, "div", value, name, propertyName);
element.className = side;
return element;
}
function createContentAreaElement(name)
{
console.assert(name === "width" || name === "height");
let size = this._getPropertyValueAsPx(style, name);
if (this._getPropertyValue(style, "box-sizing") === "border-box") {
let borderBox = this._getBox(style, "border");
let paddingBox = this._getBox(style, "padding");
let [side, oppositeSide] = name === "width" ? ["left", "right"] : ["top", "bottom"];
size = size - borderBox[side] - borderBox[oppositeSide] - paddingBox[side] - paddingBox[oppositeSide];
}
return createValueElement.call(this, "span", size, name, name);
}
// Display types for which margin is ignored.
var noMarginDisplayType = {
"table-cell": true,
"table-column": true,
"table-column-group": true,
"table-footer-group": true,
"table-header-group": true,
"table-row": true,
"table-row-group": true
};
// Display types for which padding is ignored.
var noPaddingDisplayType = {
"table-column": true,
"table-column-group": true,
"table-footer-group": true,
"table-header-group": true,
"table-row": true,
"table-row-group": true
};
// Position types for which top, left, bottom and right are ignored.
var noPositionType = {
"static": true
};
this._boxElements = [];
if (style.enabledProperties.length === 0) {
this.showEmptyMessage();
return;
}
let displayProperty = this._getPropertyValue(style, "display");
let positionProperty = this._getPropertyValue(style, "position");
if (!displayProperty || !positionProperty) {
this.showEmptyMessage();
return;
}
this._outermostBox = null;
this._outermostBoxWidth = NaN;
for (let name of ["content", "padding", "border", "margin", "position"]) {
if (name === "margin" && noMarginDisplayType[displayProperty.value])
continue;
if (name === "padding" && noPaddingDisplayType[displayProperty.value])
continue;
if (name === "position" && noPositionType[positionProperty.value])
continue;
let boxElement = document.createElement("div");
boxElement.classList.add("box", name);
boxElement._name = name;
boxElement.addEventListener("mouseover", this._highlightDOMNode.bind(this, true, name === "position" ? "all" : name), false);
this._boxElements.push(boxElement);
if (name === "content") {
let widthElement = createContentAreaElement.call(this, "width");
let heightElement = createContentAreaElement.call(this, "height");
if (!widthElement || !heightElement) {
this.showEmptyMessage();
return;
}
boxElement.append(widthElement, " \u00D7 ", heightElement);
} else {
let borderTopLeftRadiusElement = null;
let borderTopRightRadiusElement = null;
let borderBottomRightRadiusElement = null;
let borderBottomLeftRadiusElement = null;
if (name === "border") {
for (let corner of ["top-left", "top-right", "bottom-right", "bottom-left"]) {
let cornerValue = this._getPropertyValue(style, "border-" + corner + "-radius");
if (cornerValue && cornerValue !== "0px")
boxElement.classList.add("has-" + corner + "-radius");
}
borderTopLeftRadiusElement = createBoxPartElement.call(this, "border-radius", "top-left");
borderTopRightRadiusElement = createBoxPartElement.call(this, "border-radius", "top-right");
borderBottomRightRadiusElement = createBoxPartElement.call(this, "border-radius", "bottom-right");
borderBottomLeftRadiusElement = createBoxPartElement.call(this, "border-radius", "bottom-left");
}
let topElement = createBoxPartElement.call(this, name, "top");
let leftElement = createBoxPartElement.call(this, name, "left");
let rightElement = createBoxPartElement.call(this, name, "right");
let bottomElement = createBoxPartElement.call(this, name, "bottom");
if (!topElement || !leftElement || !rightElement || !bottomElement) {
this.showEmptyMessage();
return;
}
let labelElement = document.createElement("div");
labelElement.className = "label";
labelElement.textContent = name;
boxElement.appendChild(labelElement);
if (borderTopLeftRadiusElement)
boxElement.appendChild(borderTopLeftRadiusElement);
boxElement.appendChild(topElement);
if (borderTopRightRadiusElement)
boxElement.appendChild(borderTopRightRadiusElement);
boxElement.appendChild(document.createElement("br"));
boxElement.appendChild(leftElement);
if (this._outermostBox)
boxElement.appendChild(this._outermostBox);
boxElement.appendChild(rightElement);
boxElement.appendChild(document.createElement("br"));
if (borderBottomLeftRadiusElement)
boxElement.appendChild(borderBottomLeftRadiusElement);
boxElement.appendChild(bottomElement);
if (borderBottomRightRadiusElement)
boxElement.appendChild(borderBottomRightRadiusElement);
}
this._outermostBox = boxElement;
}
metricsElement.appendChild(this._outermostBox);
metricsElement.addEventListener("mouseover", this._highlightDOMNode.bind(this, false, ""), false);
this.hideEmptyMessage();
this.element.appendChild(metricsElement);
}
_startEditing(targetElement, box, styleProperty, computedStyle)
{
if (WI.isBeingEdited(targetElement))
return;
// If the target element has a title use it as the editing value
// since the current text is likely truncated/rounded.
if (targetElement.title)
targetElement.textContent = targetElement.title;
var context = {box, styleProperty};
var boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty);
context.keyDownHandler = boundKeyDown;
targetElement.addEventListener("keydown", boundKeyDown, false);
this._isEditingMetrics = true;
var config = new WI.EditingConfig(this._editingCommitted.bind(this), this._editingCancelled.bind(this), context);
WI.startEditing(targetElement, config);
window.getSelection().setBaseAndExtent(targetElement, 0, targetElement, 1);
}
_alteredFloatNumber(number, event)
{
var arrowKeyPressed = event.keyIdentifier === "Up" || event.keyIdentifier === "Down";
// Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
// Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
var changeAmount = 1;
if (event.shiftKey && !arrowKeyPressed)
changeAmount = 100;
else if (event.shiftKey || !arrowKeyPressed)
changeAmount = 10;
else if (event.altKey)
changeAmount = 0.1;
if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
changeAmount *= -1;
// Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
// Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
var result = Number((number + changeAmount).toFixed(6));
if (!String(result).match(WI.EditingSupport.NumberRegex))
return null;
return result;
}
_handleKeyDown(context, styleProperty, event)
{
if (!/^(?:Page)?(?:Up|Down)$/.test(event.keyIdentifier))
return;
var element = event.currentTarget;
var selection = window.getSelection();
if (!selection.rangeCount)
return;
var selectionRange = selection.getRangeAt(0);
console.assert(selectionRange, "We should have a range if we are handling a key down event");
if (!element.contains(selectionRange.commonAncestorContainer))
return;
var originalValue = element.textContent;
var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WI.EditingSupport.StyleValueDelimiters, element);
var wordString = wordRange.toString();
var matches = WI.EditingSupport.NumberRegex.exec(wordString);
var replacementString;
if (matches && matches.length) {
var prefix = matches[1];
var suffix = matches[3];
var number = this._alteredFloatNumber(parseFloat(matches[2]), event);
if (number === null) {
// Need to check for null explicitly.
return;
}
if (styleProperty !== "margin" && number < 0)
number = 0;
replacementString = prefix + number + suffix;
}
if (!replacementString)
return;
var replacementTextNode = document.createTextNode(replacementString);
wordRange.deleteContents();
wordRange.insertNode(replacementTextNode);
var finalSelectionRange = document.createRange();
finalSelectionRange.setStart(replacementTextNode, 0);
finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
selection.removeAllRanges();
selection.addRange(finalSelectionRange);
event.handled = true;
event.preventDefault();
this._ignoreNextRefresh = true;
this._applyUserInput(element, replacementString, originalValue, context, false);
}
_editingEnded(element, context)
{
element.removeEventListener("keydown", context.keyDownHandler, false);
this._isEditingMetrics = false;
}
_editingCancelled(element, context)
{
this._editingEnded(element, context);
this._refresh();
}
_applyUserInput(element, userInput, previousContent, context, commitEditor)
{
if (commitEditor && userInput === previousContent) {
// Nothing changed, so cancel.
this._editingCancelled(element, context);
return;
}
if (context.box !== "position" && (!userInput || userInput === figureDash))
userInput = "0px";
else if (context.box === "position" && (!userInput || userInput === figureDash))
userInput = "auto";
userInput = userInput.toLowerCase();
// Append a "px" unit if the user input was just a number.
if (/^-?(?:\d+(?:\.\d+)?|\.\d+)$/.test(userInput))
userInput += "px";
var styleProperty = context.styleProperty;
var computedStyle = this._nodeStyles.computedStyle;
if (this._getPropertyValue(computedStyle, "box-sizing") === "border-box" && (styleProperty === "width" || styleProperty === "height")) {
if (!userInput.match(/px$/)) {
console.error("For elements with box-sizing: border-box, only absolute content area dimensions can be applied");
return;
}
var borderBox = this._getBox(computedStyle, "border");
var paddingBox = this._getBox(computedStyle, "padding");
var userValuePx = Number(userInput.replace(/px$/, ""));
if (isNaN(userValuePx))
return;
if (styleProperty === "width")
userValuePx += borderBox.left + borderBox.right + paddingBox.left + paddingBox.right;
else
userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddingBox.bottom;
userInput = userValuePx + "px";
}
let setBorderStyleProperty = null;
if (context.box === "border") {
let borderStyleProperty = styleProperty.replace("-width", "-style");
if (this._getPropertyValue(computedStyle, borderStyleProperty) === "none")
setBorderStyleProperty = borderStyleProperty;
}
WI.RemoteObject.resolveNode(this._nodeStyles.node).then((object) => {
function inspectedPage_node_toggleInlineStyleProperty(styleProperty, userInput, setBorderStyleProperty) {
if (setBorderStyleProperty)
this.style.setProperty(setBorderStyleProperty, "solid", "important");
this.style.setProperty(styleProperty, userInput, "important");
}
let didToggle = () => {
this._nodeStyles.refresh();
};
object.callFunction(inspectedPage_node_toggleInlineStyleProperty, [styleProperty, userInput, setBorderStyleProperty], false, didToggle);
object.release();
});
}
_editingCommitted(element, userInput, previousContent, context)
{
this._editingEnded(element, context);
this._applyUserInput(element, userInput, previousContent, context, true);
}
};
+100
View File
@@ -0,0 +1,100 @@
/*
* 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.
*/
.box-shadow-editor {
max-width: var(--color-picker-width);
}
.box-shadow-editor > table {
width: 100%;
padding: 0 4px;
border-spacing: 0;
}
.box-shadow-editor > table > tr > th {
font-weight: bold;
text-align: end;
line-height: 23px;
vertical-align: top;
white-space: nowrap;
color: hsl(0, 0%, 34%);
}
.box-shadow-editor > table > tr > td {
padding-inline-start: 4px;
}
.box-shadow-editor > table > tr > td > input[type="text"] {
width: 100%;
padding: 3px 4px 2px;
font-family: Menlo, monospace;
text-align: end;
background-color: var(--background-color-content);
border: 1px solid var(--text-color-quaternary);
appearance: none;
}
.box-shadow-editor > table > tr > td > input[type="range"] {
width: 120px;
height: 19px;
background-color: transparent;
}
.box-shadow-editor > table > tr.offset-x > td > svg {
margin-inline-start: 5px;
}
.box-shadow-editor > table > tr.offset-x > td > svg line.axis {
fill: none;
stroke: var(--text-color-quaternary);
stroke-width: 2;
stroke-linecap: round;
}
.box-shadow-editor > table > tr.offset-x > td > svg line:not(.axis) {
fill: none;
stroke: var(--glyph-color-active);
stroke-width: 2;
stroke-linecap: round;
}
.box-shadow-editor > table > tr.offset-x > td > svg circle {
r: 5px; /* keep in sync with `this._offsetSliderKnobRadius` */
fill: var(--glyph-color-active);
}
.box-shadow-editor > table > tr.inset > td {
vertical-align: top;
}
.box-shadow-editor > table > tr.inset > td > input[type="checkbox"] {
margin-top: 0.5em;
}
@media (prefers-color-scheme: dark) {
.box-shadow-editor > table > tr > th {
color: var(--text-color-secondary);
}
}
+525
View File
@@ -0,0 +1,525 @@
/*
* 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.BoxShadowEditor = class BoxShadowEditor extends WI.Object
{
constructor()
{
super();
this._element = document.createElement("div");
this._element.classList.add("box-shadow-editor");
let tableElement = this._element.appendChild(document.createElement("table"));
function createInputRow(identifier, label) {
let id = `box-shadow-editor-${identifier}-input`;
let rowElement = tableElement.appendChild(document.createElement("tr"));
rowElement.className = identifier;
let headerElement = rowElement.appendChild(document.createElement("th"));
let labelElement = headerElement.appendChild(document.createElement("label"));
labelElement.setAttribute("for", id);
labelElement.textContent = label;
let dataElement = rowElement.appendChild(document.createElement("td"));
let inputElement = dataElement.appendChild(document.createElement("input"));
inputElement.type = "text";
inputElement.id = id;
return {rowElement, inputElement};
}
function createSlider(rowElement, min, max) {
let dataElement = rowElement.appendChild(document.createElement("td"));
let rangeElement = dataElement.appendChild(document.createElement("input"));
rangeElement.type = "range";
rangeElement.min = min;
rangeElement.max = max;
return rangeElement;
}
let offsetXRow = createInputRow("offset-x", WI.UIString("Offset X", "Offset X @ Box Shadow Editor", "Input label for the x-axis of the offset of a CSS box shadow"));
this._offsetXInput = offsetXRow.inputElement;
this._offsetXInput.spellcheck = false;
this._offsetXInput.addEventListener("input", this._handleOffsetXInputInput.bind(this));
this._offsetXInput.addEventListener("keydown", this._handleOffsetXInputKeyDown.bind(this));
let offsetSliderDataElement = offsetXRow.rowElement.appendChild(document.createElement("td"));
offsetSliderDataElement.setAttribute("rowspan", 3);
this._offsetSliderKnobRadius = 5; // keep in sync with `.box-shadow-editor > table > tr > td > svg circle`
this._offsetSliderAreaSize = 100;
const offsetSliderContainerSize = this._offsetSliderAreaSize + (this._offsetSliderKnobRadius * 2);
this._offsetSliderSVG = offsetSliderDataElement.appendChild(createSVGElement("svg"));
this._offsetSliderSVG.setAttribute("tabindex", 0);
this._offsetSliderSVG.setAttribute("width", offsetSliderContainerSize);
this._offsetSliderSVG.setAttribute("height", offsetSliderContainerSize);
this._offsetSliderSVG.addEventListener("mousedown", this);
this._offsetSliderSVG.addEventListener("keydown", this);
this._offsetSliderSVGMouseDownPoint = null;
let offsetSliderGroup = this._offsetSliderSVG.appendChild(createSVGElement("g"));
offsetSliderGroup.setAttribute("transform", `translate(${this._offsetSliderKnobRadius}, ${this._offsetSliderKnobRadius})`);
let offsetSliderXAxisLine = offsetSliderGroup.appendChild(createSVGElement("line"));
offsetSliderXAxisLine.classList.add("axis");
offsetSliderXAxisLine.setAttribute("x1", this._offsetSliderAreaSize / 2);
offsetSliderXAxisLine.setAttribute("y1", 0);
offsetSliderXAxisLine.setAttribute("x2", this._offsetSliderAreaSize / 2);
offsetSliderXAxisLine.setAttribute("y2", this._offsetSliderAreaSize);
let offsetSliderYAxisLine = offsetSliderGroup.appendChild(createSVGElement("line"));
offsetSliderYAxisLine.classList.add("axis");
offsetSliderYAxisLine.setAttribute("x1", 0);
offsetSliderYAxisLine.setAttribute("y1", this._offsetSliderAreaSize / 2);
offsetSliderYAxisLine.setAttribute("x2", this._offsetSliderAreaSize);
offsetSliderYAxisLine.setAttribute("y2", this._offsetSliderAreaSize / 2);
this._offsetSliderLine = offsetSliderGroup.appendChild(createSVGElement("line"));
this._offsetSliderLine.setAttribute("x1", this._offsetSliderAreaSize / 2);
this._offsetSliderLine.setAttribute("y1", this._offsetSliderAreaSize / 2);
this._offsetSliderKnob = offsetSliderGroup.appendChild(createSVGElement("circle"));
let offsetYRow = createInputRow("offset-y", WI.UIString("Offset Y", "Offset Y @ Box Shadow Editor", "Input label for the y-axis of the offset of a CSS box shadow"));
this._offsetYInput = offsetYRow.inputElement;
this._offsetYInput.spellcheck = false;
this._offsetYInput.addEventListener("input", this._handleOffsetYInputInput.bind(this));
this._offsetYInput.addEventListener("keydown", this._handleOffsetYInputKeyDown.bind(this));
let insetRow = createInputRow("inset", WI.UIString("Inset", "Inset @ Box Shadow Editor", "Checkbox label for the inset of a CSS box shadow."));
this._insetCheckbox = insetRow.inputElement;
this._insetCheckbox.type = "checkbox";
this._insetCheckbox.addEventListener("change", this._handleInsetCheckboxChange.bind(this));
let blurRadiusRow = createInputRow("blur-radius", WI.UIString("Blur", "Blur @ Box Shadow Editor", "Input label for the blur radius of a CSS box shadow"));
this._blurRadiusInput = blurRadiusRow.inputElement;
this._blurRadiusInput.spellcheck = false;
this._blurRadiusInput.addEventListener("input", this._handleBlurRadiusInputInput.bind(this));
this._blurRadiusInput.addEventListener("keydown", this._handleBlurRadiusInputKeyDown.bind(this));
this._blurRadiusInput.min = 0;
this._blurRadiusSlider = createSlider(blurRadiusRow.rowElement, 0, 100);
this._blurRadiusSlider.addEventListener("input", this._handleBlurRadiusSliderInput.bind(this));
let spreadRadiusRow = createInputRow("spread-radius", WI.UIString("Spread", "Spread @ Box Shadow Editor", "Input label for the spread radius of a CSS box shadow"));
this._spreadRadiusInput = spreadRadiusRow.inputElement;
this._spreadRadiusInput.spellcheck = false;
this._spreadRadiusInput.addEventListener("input", this._handleSpreadRadiusInputInput.bind(this));
this._spreadRadiusInput.addEventListener("keydown", this._handleSpreadRadiusInputKeyDown.bind(this));
this._spreadRadiusSlider = createSlider(spreadRadiusRow.rowElement, -50, 50);
this._spreadRadiusSlider.addEventListener("input", this._handleSpreadRadiusSliderInput.bind(this));
this._colorPicker = new WI.ColorPicker;
this._colorPicker.addEventListener(WI.ColorPicker.Event.ColorChanged, this._handleColorChanged, this);
this._element.appendChild(this._colorPicker.element);
this.boxShadow = new WI.BoxShadow;
WI.addWindowKeydownListener(this);
}
// Public
get element() { return this._element; }
get boxShadow()
{
return this._boxShadow;
}
set boxShadow(boxShadow)
{
console.assert(boxShadow instanceof WI.BoxShadow);
this._boxShadow = boxShadow;
let offsetX = this._boxShadow?.offsetX || {value: 0, unit: ""};
let offsetY = this._boxShadow?.offsetY || {value: 0, unit: ""};
this._offsetXInput.value = offsetX.value + offsetX.unit;
this._offsetYInput.value = offsetY.value + offsetY.unit;
let offsetSliderCenter = this._offsetSliderAreaSize / 2;
let offsetSliderX = Number.constrain(offsetX.value + offsetSliderCenter, 0, this._offsetSliderAreaSize);
let offsetSliderY = Number.constrain(offsetY.value + offsetSliderCenter, 0, this._offsetSliderAreaSize);
this._offsetSliderLine.setAttribute("x2", offsetSliderX);
this._offsetSliderKnob.setAttribute("cx", offsetSliderX);
this._offsetSliderLine.setAttribute("y2", offsetSliderY);
this._offsetSliderKnob.setAttribute("cy", offsetSliderY);
let blurRadius = this._boxShadow?.blurRadius || {value: 0, unit: ""};
this._blurRadiusInput.value = blurRadius.value + blurRadius.unit;
this._blurRadiusSlider.value = blurRadius.value;
let spreadRadius = this._boxShadow?.spreadRadius || {value: 0, unit: ""};
this._spreadRadiusInput.value = spreadRadius.value + spreadRadius.unit;
this._spreadRadiusSlider.value = spreadRadius.value;
let inset = this._boxShadow?.inset || false;
this._insetCheckbox.checked = inset;
let color = this._boxShadow?.color || WI.Color.fromString("transparent");
this._colorPicker.color = color;
}
// Protected
handleEvent(event)
{
switch (event.type) {
case "keydown":
console.assert(event.target === this._offsetSliderSVG);
this._handleOffsetSliderSVGKeyDown(event);
return;
case "mousedown":
console.assert(event.target === this._offsetSliderSVG);
this._handleOffsetSliderSVGMouseDown(event);
return;
case "mousemove":
this._handleWindowMouseMove(event);
return;
case "mouseup":
this._handleWindowMouseUp(event);
return;
}
console.assert();
}
// Private
_updateBoxShadow({offsetX, offsetY, blurRadius, spreadRadius, inset, color})
{
let change = false;
if (!offsetX)
offsetX = this._boxShadow.offsetX;
else if (!Object.shallowEqual(offsetX, this._boxShadow.offsetX))
change = true;
if (!offsetY)
offsetY = this._boxShadow.offsetY;
else if (!Object.shallowEqual(offsetY, this._boxShadow.offsetY))
change = true;
if (!blurRadius)
blurRadius = this._boxShadow.blurRadius;
else if (!Object.shallowEqual(blurRadius, this._boxShadow.blurRadius))
change = true;
if (!spreadRadius)
spreadRadius = this._boxShadow.spreadRadius;
else if (!Object.shallowEqual(spreadRadius, this._boxShadow.spreadRadius))
change = true;
if (inset === undefined)
inset = this._boxShadow.inset;
else if (inset !== this._boxShadow.inset)
change = true;
if (!color)
color = this._boxShadow.color;
else if (color.toString() !== this._boxShadow.color?.toString())
change = true;
if (!change)
return;
this.boxShadow = new WI.BoxShadow(offsetX, offsetY, blurRadius, spreadRadius, inset, color);
this.dispatchEventToListeners(WI.BoxShadowEditor.Event.BoxShadowChanged, {boxShadow: this._boxShadow});
}
_updateBoxShadowOffsetFromSliderMouseEvent(event, saveMouseDownPoint)
{
let point = WI.Point.fromEventInElement(event, this._offsetSliderSVG);
point.x = Number.constrain(point.x - this._offsetSliderKnobRadius, 0, this._offsetSliderAreaSize);
point.y = Number.constrain(point.y - this._offsetSliderKnobRadius, 0, this._offsetSliderAreaSize);
if (saveMouseDownPoint)
this._offsetSliderSVGMouseDownPoint = point;
if (event.shiftKey && this._offsetSliderSVGMouseDownPoint) {
if (Math.abs(this._offsetSliderSVGMouseDownPoint.x - point.x) > Math.abs(this._offsetSliderSVGMouseDownPoint.y - point.y))
point.y = this._offsetSliderSVGMouseDownPoint.y;
else
point.x = this._offsetSliderSVGMouseDownPoint.x;
}
let offsetSliderCenter = this._offsetSliderAreaSize / 2;
this._updateBoxShadow({
offsetX: {
value: point.x - offsetSliderCenter,
unit: this._boxShadow.offsetX.unit,
},
offsetY: {
value: point.y - offsetSliderCenter,
unit: this._boxShadow.offsetY.unit,
},
});
}
_determineShiftForEvent(event)
{
let shift = 0;
if (event.key === "ArrowUp")
shift = 1;
else if (event.key === "ArrowDown")
shift = -1;
if (!shift)
return NaN;
if (event.metaKey)
shift *= 100;
else if (event.shiftKey)
shift *= 10;
else if (event.altKey)
shift /= 10;
event.preventDefault();
return shift;
}
_handleOffsetSliderSVGKeyDown(event) {
let shiftX = 0;
let shiftY = 0;
switch (event.keyCode) {
case WI.KeyboardShortcut.Key.Up.keyCode:
shiftY = -1;
break;
case WI.KeyboardShortcut.Key.Right.keyCode:
shiftX = 1;
break;
case WI.KeyboardShortcut.Key.Down.keyCode:
shiftY = 1;
break;
case WI.KeyboardShortcut.Key.Left.keyCode:
shiftX = -1;
break;
}
if (!shiftX && !shiftY)
return false;
let multiplier = 1;
if (event.shiftKey)
multiplier = 10;
else if (event.altKey)
multiplier = 0.1;
shiftX *= multiplier;
shiftY *= multiplier;
let offsetValueLimit = this._offsetSliderAreaSize / 2;
this._updateBoxShadow({
offsetX: {
value: Number.constrain(this._boxShadow.offsetX.value + shiftX, -1 * offsetValueLimit, offsetValueLimit).maxDecimals(1),
unit: this._boxShadow.offsetX.unit,
},
offsetY: {
value: Number.constrain(this._boxShadow.offsetY.value + shiftY, -1 * offsetValueLimit, offsetValueLimit).maxDecimals(1),
unit: this._boxShadow.offsetY.unit,
},
});
}
_handleOffsetSliderSVGMouseDown(event)
{
if (event.button !== 0)
return;
event.stop();
this._offsetSliderSVG.focus();
window.addEventListener("mousemove", this, true);
window.addEventListener("mouseup", this, true);
this._updateBoxShadowOffsetFromSliderMouseEvent(event, true);
}
_handleWindowMouseMove(event)
{
this._updateBoxShadowOffsetFromSliderMouseEvent(event);
}
_handleWindowMouseUp(event)
{
this._offsetSliderSVGMouseDownPoint = null;
window.removeEventListener("mousemove", this, true);
window.removeEventListener("mouseup", this, true);
}
_handleOffsetXInputInput(event)
{
this._updateBoxShadow({
offsetX: WI.BoxShadow.parseNumberComponent(this._offsetXInput.value),
});
}
_handleOffsetXInputKeyDown(event)
{
let shift = this._determineShiftForEvent(event);
if (isNaN(shift))
return;
this._updateBoxShadow({
offsetX: {
value: (this._boxShadow.offsetX.value + shift).maxDecimals(1),
unit: this._boxShadow.offsetX.unit,
},
});
}
_handleOffsetYInputInput(event)
{
this._updateBoxShadow({
offsetY: WI.BoxShadow.parseNumberComponent(this._offsetYInput.value),
});
}
_handleOffsetYInputKeyDown(event)
{
let shift = this._determineShiftForEvent(event);
if (isNaN(shift))
return;
this._updateBoxShadow({
offsetY: {
value: (this._boxShadow.offsetY.value + shift).maxDecimals(1),
unit: this._boxShadow.offsetY.unit,
},
});
}
_handleBlurRadiusInputInput(event)
{
this._updateBoxShadow({
blurRadius: WI.BoxShadow.parseNumberComponent(this._blurRadiusInput.value),
});
}
_handleBlurRadiusInputKeyDown(event)
{
let shift = this._determineShiftForEvent(event);
if (isNaN(shift))
return;
this._updateBoxShadow({
blurRadius: {
value: (this._boxShadow.blurRadius.value + shift).maxDecimals(1),
unit: this._boxShadow.blurRadius.unit,
}
});
}
_handleBlurRadiusSliderInput(event)
{
this._updateBoxShadow({
blurRadius: {
value: this._blurRadiusSlider.valueAsNumber,
unit: this._boxShadow.blurRadius.unit,
}
});
}
_handleSpreadRadiusInputInput(event)
{
this._updateBoxShadow({
spreadRadius: WI.BoxShadow.parseNumberComponent(this._spreadRadiusInput.value),
});
}
_handleSpreadRadiusInputKeyDown(event)
{
let shift = this._determineShiftForEvent(event);
if (isNaN(shift))
return;
this._updateBoxShadow({
spreadRadius: {
value: (this._boxShadow.spreadRadius.value + shift).maxDecimals(1),
unit: this._boxShadow.spreadRadius.unit,
}
});
}
_handleSpreadRadiusSliderInput(event)
{
this._updateBoxShadow({
spreadRadius: {
value: this._spreadRadiusSlider.valueAsNumber,
unit: this._boxShadow.spreadRadius.unit,
}
});
}
_handleInsetCheckboxChange(event)
{
this._updateBoxShadow({
inset: !!this._insetCheckbox.checked,
});
}
_handleColorChanged(event)
{
this._updateBoxShadow({
color: this._colorPicker.color,
});
}
};
WI.BoxShadowEditor.Event = {
BoxShadowChanged: "box-shadow-editor-box-shadow-changed"
};
+121
View File
@@ -0,0 +1,121 @@
/*
* Copyright (C) 2013-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.
*/
.breakpoint-action-block-header {
margin-bottom: 3px;
}
body[dir=ltr] .breakpoint-action-button-container {
float: right;
}
body[dir=rtl] .breakpoint-action-button-container {
float: left;
}
:matches(.breakpoint-action-append-button, .breakpoint-action-remove-button) {
background-origin: border-box;
width: 13px;
height: 13px;
margin-block-start: 5px;
margin-inline-start: 5px;
background-color: transparent;
border: none;
opacity: 0.6;
}
.breakpoint-action-append-button {
background-image: url(../Images/Plus13.svg);
}
.breakpoint-action-remove-button {
background-image: url(../Images/Minus.svg);
}
.breakpoint-action-block-body {
margin: 1px 2px 6px 2px;
padding: 8px;
width: 100%;
border-radius: 4px;
border: 1px solid hsl(0, 0%, 74%);
background-color: hsla(0, 0%, 87%, 0.95);
box-shadow: inset 0 0 2px hsl(0, 0%, 78%);
}
.breakpoint-action-block-body .description {
flex-grow: 1;
text-align: right;
color: var(--text-color-gray-medium);
}
.breakpoint-action-block-body > input {
width: 100%;
text-align: left;
}
.breakpoint-action-block-body > .flex {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.breakpoint-action-eval-editor {
margin: 2px 0;
padding: 2px 0 1px 0;
appearance: textfield;
border: 1px solid hsl(0, 0%, 78%);
background: white;
}
.breakpoint-action-eval-editor > .CodeMirror {
width: 336px; /* NOTE: Fixed value, manually tuned to .edit-breakpoint-popover-content.wide width */
height: auto;
}
.breakpoint-action-eval-editor > .CodeMirror-scroll {
width: 336px; /* NOTE: Fixed value, manually tuned to .edit-breakpoint-popover-content.wide width */
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
.breakpoint-action-block-body {
border-color: var(--text-color-quaternary);
background-color: unset;
box-shadow: unset;
}
.breakpoint-action-block-body > .description {
color: var(--text-color-secondary);
}
.breakpoint-action-append-button,
.breakpoint-action-remove-button {
filter: invert();
}
.breakpoint-action-eval-editor {
background: var(--background-color-content);
}
}
+235
View File
@@ -0,0 +1,235 @@
/*
* Copyright (C) 2013, 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.BreakpointActionView = class BreakpointActionView extends WI.Object
{
constructor(action, delegate, {omitFocus} = {})
{
super();
console.assert(action);
console.assert(delegate);
this._action = action;
this._delegate = delegate;
this._element = document.createElement("div");
this._element.className = "breakpoint-action-block";
var header = this._element.appendChild(document.createElement("div"));
header.className = "breakpoint-action-block-header";
var picker = header.appendChild(document.createElement("select"));
picker.addEventListener("change", this._pickerChanged.bind(this));
for (var key in WI.BreakpointAction.Type) {
var type = WI.BreakpointAction.Type[key];
var option = document.createElement("option");
option.textContent = WI.BreakpointActionView.displayStringForType(type);
option.selected = this._action.type === type;
option.value = type;
picker.add(option);
}
let buttonContainerElement = header.appendChild(document.createElement("div"));
buttonContainerElement.classList.add("breakpoint-action-button-container");
let appendActionButton = buttonContainerElement.appendChild(document.createElement("button"));
appendActionButton.className = "breakpoint-action-append-button";
appendActionButton.addEventListener("click", this._appendActionButtonClicked.bind(this));
appendActionButton.title = WI.UIString("Add new breakpoint action after this action");
let removeActionButton = buttonContainerElement.appendChild(document.createElement("button"));
removeActionButton.className = "breakpoint-action-remove-button";
removeActionButton.addEventListener("click", this._removeAction.bind(this));
removeActionButton.title = WI.UIString("Delete this breakpoint action");
this._bodyElement = this._element.appendChild(document.createElement("div"));
this._bodyElement.className = "breakpoint-action-block-body";
this._updateBody(omitFocus);
}
// Static
static displayStringForType(type)
{
switch (type) {
case WI.BreakpointAction.Type.Log:
return WI.UIString("Log Message");
case WI.BreakpointAction.Type.Evaluate:
return WI.UIString("Evaluate JavaScript");
case WI.BreakpointAction.Type.Sound:
return WI.UIString("Play Sound");
case WI.BreakpointAction.Type.Probe:
return WI.UIString("Probe Expression");
default:
console.assert(false);
return "";
}
}
// Public
get action()
{
return this._action;
}
get element()
{
return this._element;
}
// Private
_pickerChanged(event)
{
this._action.type = event.target.value;
this._updateBody();
this._delegate.breakpointActionViewResized(this);
}
_appendActionButtonClicked(event)
{
this._delegate.breakpointActionViewAppendActionView(this, new WI.BreakpointAction(this._action.type));
}
_removeAction()
{
this._delegate.breakpointActionViewRemoveActionView(this);
}
_updateBody(omitFocus)
{
this._bodyElement.removeChildren();
let createOptionsElements = () => {
let optionsElement = document.createElement("div");
let emulateUserGestureLabel = optionsElement.appendChild(document.createElement("label"));
this._emulateUserGestureCheckbox = emulateUserGestureLabel.appendChild(document.createElement("input"));
this._emulateUserGestureCheckbox.type = "checkbox";
this._emulateUserGestureCheckbox.checked = this._action.emulateUserGesture;
this._emulateUserGestureCheckbox.addEventListener("change", this._handleEmulateUserGestureCheckboxChange.bind(this));
emulateUserGestureLabel.appendChild(document.createTextNode(WI.UIString("Emulate User Gesture", "Emulate User Gesture @ breakpoint action configuration", "Checkbox shown when configuring log/evaluate/probe breakpoint actions to cause it to be evaluated as though it was in response to user interaction.")));
return optionsElement;
};
switch (this._action.type) {
case WI.BreakpointAction.Type.Log:
this._bodyElement.hidden = false;
var input = this._bodyElement.appendChild(document.createElement("input"));
input.placeholder = WI.UIString("Message");
input.addEventListener("input", this._handleLogInputInput.bind(this));
input.value = this._action.data || "";
input.spellcheck = false;
if (!omitFocus)
setTimeout(function() { input.focus(); }, 0);
var flexWrapper = this._bodyElement.appendChild(document.createElement("div"));
flexWrapper.className = "flex";
if (WI.BreakpointAction.supportsEmulateUserAction())
flexWrapper.appendChild(createOptionsElements());
var descriptionElement = flexWrapper.appendChild(document.createElement("div"));
descriptionElement.classList.add("description");
descriptionElement.setAttribute("dir", "ltr");
descriptionElement.textContent = WI.UIString("${expr} = expression");
break;
case WI.BreakpointAction.Type.Evaluate:
case WI.BreakpointAction.Type.Probe:
this._bodyElement.hidden = false;
var editorElement = this._bodyElement.appendChild(document.createElement("div"));
editorElement.classList.add("breakpoint-action-eval-editor");
editorElement.classList.add(WI.SyntaxHighlightedStyleClassName);
this._codeMirror = WI.CodeMirrorEditor.create(editorElement, {
lineWrapping: true,
mode: "text/javascript",
matchBrackets: true,
value: this._action.data || "",
});
this._codeMirrorClientHeight = NaN;
this._codeMirror.on("changes", this._handleJavaScriptCodeMirrorChanges.bind(this));
var completionController = new WI.CodeMirrorCompletionController(this._delegate.breakpointActionViewCodeMirrorCompletionControllerMode(this, this._codeMirror), this._codeMirror);
completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider);
if (WI.BreakpointAction.supportsEmulateUserAction())
this._bodyElement.appendChild(createOptionsElements());
// CodeMirror needs a refresh after the popover displays to layout otherwise it doesn't appear.
setTimeout(() => {
this._codeMirror.refresh();
if (!omitFocus)
this._codeMirror.focus();
}, 0);
break;
case WI.BreakpointAction.Type.Sound:
this._bodyElement.hidden = true;
break;
default:
console.assert(false);
this._bodyElement.hidden = true;
break;
}
}
_handleLogInputInput(event)
{
this._action.data = event.target.value;
}
_handleJavaScriptCodeMirrorChanges(codeMirror, changes)
{
// Throw away the expression if it's just whitespace.
this._action.data = this._codeMirror.getValue().trim();
let {clientHeight} = this._codeMirror.getScrollInfo();
if (clientHeight !== this._codeMirrorClientHeight) {
this._codeMirrorClientHeight = clientHeight;
this._delegate.breakpointActionViewResized(this);
}
}
_handleEmulateUserGestureCheckboxChange(event)
{
this._action.emulateUserGesture = this._emulateUserGestureCheckbox.checked;
}
};
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 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.
*/
.inline-widget.breakpoint {
display: inline-block;
position: relative;
width: 10px;
height: 10px;
margin-inline-end: 1px;
vertical-align: -1px;
clip-path: path(evenodd, "M 0 0 L 5 0 L 10 5 L 5 10 L 0 10 Z");
cursor: default;
}
.inline-widget.breakpoint.placeholder::before {
display: block;
width: 100%;
height: 100%;
content: "";
background-color: var(--breakpoint-color);
opacity: 0.7;
clip-path: path(evenodd, "M 0 0 L 5 0 L 10 5 L 5 10 L 0 10 M 1 1 L 4.5 1 L 8.5 5 L 4.5 9 L 1 9 Z");
}
.inline-widget.breakpoint:not(.resolved) {
filter: grayscale();
}
.inline-widget.breakpoint:not(.placeholder, .disabled) {
background-color: var(--breakpoint-color);
}
.inline-widget.breakpoint.disabled {
background-color: var(--breakpoint-color-disabled);
}
.inline-widget.breakpoint.auto-continue::after {
position: absolute;
top: 2px;
right: 2px;
width: 3px;
height: 6px;
content: "";
clip-path: polygon(0 0, 100% 50%, 0 100%);
background-color: var(--selected-foreground-color);
}
+155
View File
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2022 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.BreakpointInlineWidget = class BreakpointInlineWidget
{
constructor(breakpointOrSourceCodeLocation)
{
if (breakpointOrSourceCodeLocation instanceof WI.JavaScriptBreakpoint) {
console.assert(breakpointOrSourceCodeLocation instanceof WI.JavaScriptBreakpoint, breakpointOrSourceCodeLocation);
console.assert(!breakpointOrSourceCodeLocation.special, breakpointOrSourceCodeLocation);
console.assert(breakpointOrSourceCodeLocation.resolved, breakpointOrSourceCodeLocation);
this._breakpoint = breakpointOrSourceCodeLocation;
this._sourceCodeLocation = this._breakpoint.sourceCodeLocation;
this._addBreakpointEventListeners();
} else {
console.assert(breakpointOrSourceCodeLocation instanceof WI.SourceCodeLocation, breakpointOrSourceCodeLocation);
this._sourceCodeLocation = breakpointOrSourceCodeLocation;
this._breakpoint = null;
}
this._element = document.createElement("span");
this._element.classList.add("inline-widget", "breakpoint");
this._element.addEventListener("click", this._handleClick.bind(this));
this._element.addEventListener("contextmenu", this._handleContextmenu.bind(this));
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this._handleBreakpointsEnabledDidChange, this);
this._update();
}
// Public
get element() { return this._element; }
get sourceCodeLocation() { return this._sourceCodeLocation; }
get breakpoint() { return this._breakpoint; }
// Private
_update()
{
this._element.classList.toggle("placeholder", !this._breakpoint);
this._element.classList.toggle("resolved", this._breakpoint?.resolved || WI.debuggerManager.breakpointsEnabled);
this._element.classList.toggle("disabled", !!this._breakpoint?.disabled);
this._element.classList.toggle("auto-continue", !!this._breakpoint?.autoContinue);
}
_createBreakpoint() {
console.assert(!this._breakpoint);
this._breakpoint = new WI.JavaScriptBreakpoint(this._sourceCodeLocation, {resolved: true});
WI.debuggerManager.addBreakpoint(this._breakpoint);
this._addBreakpointEventListeners();
this._update();
}
_addBreakpointEventListeners()
{
this._breakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleBreakpointDisabledStateChanged, this);
this._breakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleBreakpointAutoContinueChanged, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointRemoved, this._handleBreakpointRemoved, this);
}
_handleClick(event)
{
if (this._breakpoint) {
this._breakpoint.disabled = !this._breakpoint.disabled;
return;
}
this._createBreakpoint();
}
_handleContextmenu(event)
{
let contextMenu = WI.ContextMenu.createFromEvent(event);
if (!this._breakpoint) {
contextMenu.appendItem(WI.UIString("Add Breakpoint"), () => {
this._createBreakpoint();
});
return;
}
WI.BreakpointPopover.appendContextMenuItems(contextMenu, this._breakpoint, this._element);
if (!WI.isShowingSourcesTab()) {
contextMenu.appendSeparator();
contextMenu.appendItem(WI.UIString("Reveal in Sources Tab"), () => {
WI.showSourcesTab({
representedObjectToSelect: this._breakpoint,
initiatorHint: WI.TabBrowser.TabNavigationInitiator.ContextMenu,
});
});
}
}
_handleBreakpointsEnabledDidChange(event)
{
this._update();
}
_handleBreakpointDisabledStateChanged(event)
{
this._update();
}
_handleBreakpointAutoContinueChanged(event)
{
this._update();
}
_handleBreakpointRemoved(event)
{
let {breakpoint} = event.data;
if (breakpoint !== this._breakpoint)
return;
this._breakpoint.removeEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleBreakpointDisabledStateChanged, this);
this._breakpoint.removeEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleBreakpointAutoContinueChanged, this);
WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.BreakpointRemoved, this._handleBreakpointRemoved, this);
this._breakpoint = null;
this._update();
}
};
+108
View File
@@ -0,0 +1,108 @@
/*
* Copyright (C) 2013-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.
*/
.popover .edit-breakpoint-popover-content {
position: relative;
max-width: 420px;
padding: 5px 15px;
}
.popover .edit-breakpoint-popover-content.wide {
max-width: 460px;
}
.popover .edit-breakpoint-popover-content > label.toggle {
font-weight: bold;
text-overflow: ellipsis;
white-space: nowrap;
}
.popover .edit-breakpoint-popover-content > table {
width: 100%;
}
.popover .edit-breakpoint-popover-content > table > tr > th {
font-weight: bold;
line-height: 23px;
text-align: end;
vertical-align: top;
white-space: nowrap;
color: hsl(0, 0%, 34%);
}
.popover .edit-breakpoint-popover-content > table > tr > td {
padding-left: 5px;
}
.popover .edit-breakpoint-popover-content > table > tr > td > .editor {
width: 100%;
padding: 4px 0 2px;
appearance: textfield;
border: 1px solid hsl(0, 0%, 78%);
background: var(--background-color-content);
}
.popover .edit-breakpoint-popover-content > table > tr:last-child > td > .editor {
margin-inline-end: calc(var(--reference-page-link-size) + 4px);
}
.popover .edit-breakpoint-popover-content > table > tr > td > .editor > .CodeMirror {
width: 320px; /* NOTE: Fixed value, manually tuned to .edit-breakpoint-popover-content width. */
height: auto;
}
.popover .edit-breakpoint-popover-content.wide > table > tr > td > .editor > .CodeMirror {
width: 360px; /* NOTE: Fixed value, manually tuned to .edit-breakpoint-popover-content width. */
}
.popover .edit-breakpoint-popover-content > table > tr > td > .editor + label + label {
margin-inline-start: 8px;
}
.popover .edit-breakpoint-popover-content input#edit-breakpoint-popover-ignore-count {
width: 40px;
}
.popover .edit-breakpoint-popover-content input:is(#edit-breakpoint-popover-ignore-count, #edit-breakpoint-popover-auto-continue) {
margin-inline: 0 4px;
}
.popover .edit-breakpoint-popover-content > .reference-page-link-container {
position: absolute;
bottom: 0.9em;
inset-inline-end: 1.5em;
}
@media (prefers-color-scheme: dark) {
.popover .edit-breakpoint-popover-content > table > tr > th {
color: var(--text-color-secondary);
}
.popover .edit-breakpoint-popover-content > table > tr > td > .editor {
appearance: unset;
border-color: var(--text-color-quaternary);
}
}
+473
View File
@@ -0,0 +1,473 @@
/*
* 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.BreakpointPopover = class BreakpointPopover extends WI.Popover
{
constructor(delegate, breakpoint)
{
console.assert(!breakpoint || breakpoint instanceof WI.Breakpoint, breakpoint);
super(delegate);
this._breakpoint = breakpoint || null;
console.assert(this._breakpoint?.constructor.ReferencePage || this.constructor.ReferencePage, "Should have a link to a reference page.");
this._contentElement = null;
this._conditionCodeMirror = null;
this._ignoreCountInputElement = null;
this._actionsContainerElement = null;
this._actionViews = [];
this._optionsRowElement = null;
this._autoContinueCheckboxElement = null;
this._targetElement = null;
this.windowResizeHandler = this._presentOverTargetElement.bind(this);
}
// Static
static show(breakpoint, targetElement)
{
const delegate = null;
let popover;
if (breakpoint instanceof WI.EventBreakpoint)
popover = new WI.EventBreakpointPopover(delegate, breakpoint);
else if (breakpoint instanceof WI.URLBreakpoint)
popover = new WI.URLBreakpointPopover(delegate, breakpoint);
else if (breakpoint instanceof WI.SymbolicBreakpoint)
popover = new WI.SymbolicBreakpointPopover(delegate, breakpoint);
else
popover = new WI.BreakpointPopover(delegate, breakpoint);
popover.show(targetElement);
}
static appendContextMenuItems(contextMenu, breakpoint, targetElement)
{
if (breakpoint.editable && targetElement) {
contextMenu.appendItem(WI.UIString("Edit Breakpoint\u2026"), () => {
WI.BreakpointPopover.show(breakpoint, targetElement);
});
}
if (!breakpoint.disabled) {
contextMenu.appendItem(WI.UIString("Disable Breakpoint"), () => {
breakpoint.disabled = !breakpoint.disabled;
});
if (breakpoint.editable && breakpoint.autoContinue) {
contextMenu.appendItem(WI.UIString("Cancel Automatic Continue"), () => {
breakpoint.autoContinue = !breakpoint.autoContinue;
});
}
} else {
contextMenu.appendItem(WI.UIString("Enable Breakpoint"), () => {
breakpoint.disabled = !breakpoint.disabled;
});
}
if (breakpoint.editable && !breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length) {
contextMenu.appendItem(WI.UIString("Set to Automatically Continue"), () => {
breakpoint.autoContinue = !breakpoint.autoContinue;
});
}
if (breakpoint.removable) {
contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => {
breakpoint.remove();
});
} else {
contextMenu.appendItem(WI.UIString("Reset Breakpoint", "Reset Breakpoint @ Breakpoint Context Menu", "Context menu action for resetting the breakpoint to its initial configuration."), () => {
breakpoint.reset();
});
}
if (breakpoint instanceof WI.JavaScriptBreakpoint && breakpoint.sourceCodeLocation.hasMappedLocation()) {
contextMenu.appendSeparator();
contextMenu.appendItem(WI.UIString("Reveal in Original Resource"), () => {
const options = {
ignoreNetworkTab: true,
ignoreSearchTab: true,
};
WI.showOriginalOrFormattedSourceCodeLocation(breakpoint.sourceCodeLocation, options);
});
}
}
// Public
get breakpoint() { return this._breakpoint; }
show(targetElement)
{
this._targetElement = targetElement;
this._contentElement = document.createElement("div");
this._contentElement.classList.add("edit-breakpoint-popover-content");
if (this._breakpoint) {
let toggleLabelElement = this._contentElement.appendChild(document.createElement("label"));
toggleLabelElement.className = "toggle";
let toggleCheckboxElement = toggleLabelElement.appendChild(document.createElement("input"));
toggleCheckboxElement.type = "checkbox";
toggleCheckboxElement.checked = !this._breakpoint.disabled;
toggleCheckboxElement.addEventListener("change", this._handleEnabledCheckboxChange.bind(this));
toggleLabelElement.appendChild(document.createTextNode(this._breakpoint.displayName));
}
this._tableElement = this._contentElement.appendChild(document.createElement("table"));
if (!this._breakpoint)
this.populateContent();
if (this._breakpoint?.editable || this.constructor.supportsEditing) {
let conditionLabelElement = document.createElement("label");
conditionLabelElement.textContent = WI.UIString("Condition");
let conditionEditorElement = document.createElement("div");
conditionEditorElement.classList.add("editor", 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 = "edit-breakpoint-popover-content-condition";
conditionLabelElement.setAttribute("for", conditionCodeMirrorInputField.id);
this.addRow("condition", conditionLabelElement, conditionEditorElement);
this._conditionCodeMirror.addKeyMap({
"Enter": () => { this.dismiss(); },
"Esc": () => { this.dismiss(); },
});
this._conditionCodeMirror.on("beforeChange", this._handleConditionCodeMirrorBeforeChange.bind(this));
if (this._breakpoint)
this._conditionCodeMirror.on("change", this._handleConditionCodeMirrorChange.bind(this));
let completionController = new WI.CodeMirrorCompletionController(this.codeMirrorCompletionControllerMode, this._conditionCodeMirror, this);
completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider);
let ignoreCountLabelElement = document.createElement("label");
ignoreCountLabelElement.textContent = WI.UIString("Ignore");
let ignoreCountContentFragment = document.createDocumentFragment();
this._ignoreCountInputElement = ignoreCountContentFragment.appendChild(document.createElement("input"));
this._ignoreCountInputElement.id = "edit-breakpoint-popover-ignore-count";
this._ignoreCountInputElement.type = "number";
this._ignoreCountInputElement.min = 0;
this._ignoreCountInputElement.value = this._breakpoint?.ignoreCount || 0;
this._ignoreCountInputElement.addEventListener("change", this._handleIgnoreCountInputChange.bind(this));
this._ignoreCountText = ignoreCountContentFragment.appendChild(document.createElement("label"));
this._updateIgnoreCountText();
ignoreCountLabelElement.setAttribute("for", this._ignoreCountInputElement.id);
this._ignoreCountText.setAttribute("for", this._ignoreCountInputElement.id);
this.addRow("ignore-count", ignoreCountLabelElement, ignoreCountContentFragment);
let actionsLabelElement = document.createElement("label");
actionsLabelElement.textContent = WI.UIString("Action");
this._actionsContainerElement = document.createElement("div");
if (!this._breakpoint?.actions.length)
this._createAddActionButton();
else {
this._contentElement.classList.add("wide");
for (let i = 0; i < this._breakpoint.actions.length; ++i) {
let breakpointActionView = new WI.BreakpointActionView(this._breakpoint.actions[i], this, {omitFocus: true});
this._insertBreakpointActionView(breakpointActionView);
}
}
this.addRow("actions", actionsLabelElement, this._actionsContainerElement);
let optionsLabelElement = document.createElement("label");
optionsLabelElement.textContent = WI.UIString("Options");
let optionsDocumentFragment = document.createDocumentFragment();
this._autoContinueCheckboxElement = optionsDocumentFragment.appendChild(document.createElement("input"));
this._autoContinueCheckboxElement.id = "edit-breakpoint-popover-auto-continue";
this._autoContinueCheckboxElement.type = "checkbox";
this._autoContinueCheckboxElement.checked = this._breakpoint?.autoContinue || false;
this._autoContinueCheckboxElement.addEventListener("change", this._handleAutoContinueCheckboxChange.bind(this));
let optionsCheckboxLabel = optionsDocumentFragment.appendChild(document.createElement("label"));
optionsCheckboxLabel.textContent = WI.UIString("Automatically continue after evaluating");
optionsLabelElement.setAttribute("for", this._autoContinueCheckboxElement.id);
optionsCheckboxLabel.setAttribute("for", this._autoContinueCheckboxElement.id);
this._optionsRowElement = this.addRow("options", optionsLabelElement, optionsDocumentFragment);
if (!this._breakpoint?.actions.length)
this._optionsRowElement.classList.add("hidden");
// CodeMirror needs to refresh after the popover is shown as otherwise it doesn't appear.
setTimeout(() => {
this._conditionCodeMirror.refresh();
if (this._breakpoint)
this._conditionCodeMirror.focus();
this.update();
});
}
let referencePage = this._breakpoint?.constructor.ReferencePage || this.constructor.ReferencePage;
if (this._breakpoint)
referencePage = referencePage.Configuration;
this._contentElement.appendChild(referencePage.createLinkElement());
this.content = this._contentElement;
this._presentOverTargetElement();
}
dismiss()
{
this._breakpoint ??= this.createBreakpoint({
condition: this._conditionCodeMirror ? this._conditionCodeMirror.getValue().trim() : "",
actions: this._actionViews.map((breakpointActionView) => breakpointActionView.action),
ignoreCount: this._ignoreCountInputElement ? this._parseIgnoreCountNumber() : 0,
autoContinue: this._autoContinueCheckboxElement ? this._autoContinueCheckboxElement.checked : false,
});
// Remove Evaluate and Probe actions that have no data.
let emptyActions = this._breakpoint?.actions.filter(function(action) {
if (action.type === WI.BreakpointAction.Type.Sound)
return false;
return !action.data?.trim();
}) || [];
for (let action of emptyActions)
this._breakpoint.removeAction(action);
super.dismiss();
}
// CodeMirrorCompletionController delegate
completionControllerShouldAllowEscapeCompletion()
{
return false;
}
// BreakpointActionView delegate
breakpointActionViewCodeMirrorCompletionControllerMode(breakpointActionView, codeMirror)
{
return this.codeMirrorCompletionControllerMode;
}
breakpointActionViewAppendActionView(breakpointActionView, newBreakpointAction)
{
this._breakpoint?.addAction(newBreakpointAction, {precedingAction: breakpointActionView.action});
let newBreakpointActionView = new WI.BreakpointActionView(newBreakpointAction, this);
let index = this._actionViews.indexOf(breakpointActionView) + 1;
this._insertBreakpointActionView(newBreakpointActionView, index);
this._optionsRowElement.classList.remove("hidden");
this.update();
}
breakpointActionViewRemoveActionView(breakpointActionView)
{
this._breakpoint?.removeAction(breakpointActionView.action);
breakpointActionView.element.remove();
this._actionViews.remove(breakpointActionView);
if (!this._actionViews.length) {
this._createAddActionButton();
this._optionsRowElement.classList.add("hidden");
this._autoContinueCheckboxElement.checked = false;
}
this.update();
}
breakpointActionViewResized(breakpointActionView)
{
this.update();
}
// Protected
get codeMirrorCompletionControllerMode()
{
// Overridden by subclasses if needed.
if (this._breakpoint === WI.debuggerManager.allExceptionsBreakpoint || this._breakpoint === WI.debuggerManager.uncaughtExceptionsBreakpoint)
return WI.CodeMirrorCompletionController.Mode.ExceptionBreakpoint;
return WI.CodeMirrorCompletionController.Mode.Basic;
}
populateContent()
{
throw WI.NotImplementedError.subclassMustOverride();
}
addRow(className, label, content)
{
let rowElement = this._tableElement.appendChild(document.createElement("tr"));
rowElement.className = className;
let headerElement = rowElement.appendChild(document.createElement("th"));
headerElement.append(label);
let dataElement = rowElement.appendChild(document.createElement("td"));
dataElement.append(content);
return rowElement;
}
createBreakpoint(options = {})
{
throw WI.NotImplementedError.subclassMustOverride();
}
// Private
_presentOverTargetElement()
{
if (!this._targetElement)
return;
let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
this.present(targetFrame.pad(2), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]);
}
_parseIgnoreCountNumber()
{
let ignoreCount = 0;
if (this._ignoreCountInputElement.value) {
ignoreCount = parseInt(this._ignoreCountInputElement.value, 10);
if (isNaN(ignoreCount) || ignoreCount < 0)
ignoreCount = 0;
}
return ignoreCount;
}
_updateIgnoreCountText()
{
if (this._parseIgnoreCountNumber() === 1)
this._ignoreCountText.textContent = WI.UIString("time before stopping");
else
this._ignoreCountText.textContent = WI.UIString("times before stopping");
}
_createAddActionButton()
{
this._contentElement.classList.remove("wide");
this._actionsContainerElement.removeChildren();
let addActionButton = this._actionsContainerElement.appendChild(document.createElement("button"));
addActionButton.textContent = WI.UIString("Add Action");
addActionButton.addEventListener("click", this._handleAddActionButtonClick.bind(this));
}
_insertBreakpointActionView(breakpointActionView, index = this._actionViews.length)
{
if (index >= this._actionViews.length) {
this._actionsContainerElement.appendChild(breakpointActionView.element);
this._actionViews.push(breakpointActionView);
} else {
this._actionsContainerElement.insertBefore(breakpointActionView.element, this._actionViews[index].element);
this._actionViews.splice(index, 0, breakpointActionView)
}
}
_handleEnabledCheckboxChange(event)
{
this._breakpoint.disabled = !event.target.checked;
}
_handleConditionCodeMirrorBeforeChange(codeMirror, change)
{
if (change.update) {
let newText = change.text.join("").replace(/\n/g, "");
change.update(change.from, change.to, [newText]);
}
return true;
}
_handleConditionCodeMirrorChange(codeMirror, change)
{
this._breakpoint.condition = this._conditionCodeMirror.getValue().trim();
}
_handleIgnoreCountInputChange(event)
{
let ignoreCount = this._parseIgnoreCountNumber();
this._ignoreCountInputElement.value = ignoreCount;
if (this._breakpoint)
this._breakpoint.ignoreCount = ignoreCount;
this._updateIgnoreCountText();
}
_handleAddActionButtonClick(event)
{
this._contentElement.classList.add("wide");
this._actionsContainerElement.removeChildren();
let action = new WI.BreakpointAction(WI.BreakpointAction.Type.Evaluate);
this._breakpoint?.addAction(action);
let breakpointActionView = new WI.BreakpointActionView(action, this);
this._insertBreakpointActionView(breakpointActionView);
this._optionsRowElement.classList.remove("hidden");
setTimeout(() => {
this.update();
});
}
_handleAutoContinueCheckboxChange(event)
{
if (this._breakpoint)
this._breakpoint.autoContinue = this._autoContinueCheckboxElement.checked;
}
};
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2013-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.
*/
.popover .edit-breakpoint-popover-content {
width: 420px;
padding: 5px 15px;
}
.popover .edit-breakpoint-popover-content.wide {
width: 460px;
}
.popover .edit-breakpoint-popover-content > label.toggle {
font-weight: bold;
text-overflow: ellipsis;
white-space: nowrap;
}
.popover .edit-breakpoint-popover-content > table {
width: 100%;
}
.popover .edit-breakpoint-popover-content > table > tr > th {
width: 1px; /* Shrink to fit. */
font-weight: bold;
line-height: 23px;
text-align: end;
vertical-align: top;
white-space: nowrap;
color: hsl(0, 0%, 34%);
}
.popover .edit-breakpoint-popover-content > table > tr > td {
padding-left: 5px;
}
.edit-breakpoint-popover-condition {
width: 100%;
padding: 4px 0 2px 0;
-webkit-appearance: textfield;
border: 1px solid hsl(0, 0%, 78%);
background: var(--background-color-code);
}
.edit-breakpoint-popover-condition > .CodeMirror {
width: 320px; /* NOTE: Fixed value, manually tuned to .edit-breakpoint-popover-content width. */
height: auto;
}
.wide .edit-breakpoint-popover-condition > .CodeMirror {
width: 360px; /* NOTE: Fixed value, manually tuned to .edit-breakpoint-popover-content width. */
}
#edit-breakpoint-popover-ignore {
width: 40px;
}
#edit-breakpoint-popover-ignore,
#edit-breakpoint-popover-auto-continue {
-webkit-margin-start: 0;
-webkit-margin-end: 4px;
}
@media (prefers-color-scheme: dark) {
.popover .edit-breakpoint-popover-content > table > tr > th {
color: var(--text-color-secondary);
}
}
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2013-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.
*/
.item.breakpoint .status {
position: relative;
}
.item.breakpoint .status > .status-image {
width: 23px;
height: 12px;
margin-top: 2px;
fill: var(--breakpoint-color);
stroke: hsla(0, 0%, 10%, 0.3);
}
.item.breakpoint .status > .status-image.auto-continue::after {
position: absolute;
right: 3px;
width: 3px;
height: 12px;
content: "";
clip-path: polygon(0 20%, 100% 50%, 0 80%);
background-color: var(--selected-foreground-color);
}
.item.breakpoint .status > .status-image.disabled {
fill: var(--breakpoint-color-disabled);
}
.item.breakpoint .status > .status-image:not(.resolved) {
filter: grayscale();
}
body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.breakpoint.selected .status > .status-image.resolved {
stroke: var(--selected-foreground-color);
}
.item.breakpoint.paused .icon {
content: url(../Images/TypeIcons.svg#PausedBreakpoint-light);
}
@media (prefers-color-scheme: dark) {
.item.breakpoint.paused .icon {
content: url(../Images/TypeIcons.svg#PausedBreakpoint-dark);
}
}
+228
View File
@@ -0,0 +1,228 @@
/*
* Copyright (C) 2013-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.BreakpointTreeElement = class BreakpointTreeElement extends WI.GeneralTreeElement
{
constructor(breakpoint, {classNames, title, subtitle} = {})
{
console.assert(breakpoint instanceof WI.Breakpoint);
if (!Array.isArray(classNames))
classNames = [];
classNames.push("breakpoint");
super(classNames, title, subtitle, breakpoint);
// This class should not be instantiated directly. Create a concrete subclass instead.
console.assert(this.constructor !== WI.BreakpointTreeElement && this instanceof WI.BreakpointTreeElement);
this._breakpoint = breakpoint;
this._probeSet = null;
this.status = WI.ImageUtilities.useSVGSymbol("Images/Breakpoint.svg");
this.status.className = "status-image";
this.status.addEventListener("mousedown", this._statusImageElementMouseDown.bind(this));
this.status.addEventListener("click", this._statusImageElementClicked.bind(this));
this.status.addEventListener("dblclick", this._handleStatusImageElementDoubleClicked.bind(this));
this.updateStatus();
this._iconAnimationLayerElement = document.createElement("span");
this.iconElement.appendChild(this._iconAnimationLayerElement);
this.tooltipHandledSeparately = true;
}
// Public
get breakpoint()
{
return this._breakpoint;
}
ondelete()
{
// We set this flag so that TreeOutlines that will remove this
// BreakpointTreeElement will know whether it was deleted from
// within the TreeOutline or from outside it (e.g. TextEditor).
this.__deletedViaDeleteKeyboardShortcut = true;
if (this._breakpoint.removable) {
this._breakpoint.remove();
return true;
}
if (this._breakpoint.disabled)
InspectorFrontendHost.beep();
else {
this._breakpoint.disabled = true;
this._breakpoint.reset();
}
return false;
}
onenter()
{
this._breakpoint.disabled = !this._breakpoint.disabled;
return true;
}
onspace()
{
this._breakpoint.disabled = !this._breakpoint.disabled;
return true;
}
onattach()
{
super.onattach();
this._breakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this.updateStatus, this);
this._breakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this.updateStatus, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this.updateStatus, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ProbeSetAdded, this._probeSetAdded, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ProbeSetRemoved, this._probeSetRemoved, this);
for (var probeSet of WI.debuggerManager.probeSets)
if (probeSet.breakpoint === this._breakpoint)
this._addProbeSet(probeSet);
}
ondetach()
{
super.ondetach();
this._breakpoint.removeEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this.updateStatus, this);
this._breakpoint.removeEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this.updateStatus, this);
WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this.updateStatus, this);
WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.ProbeSetAdded, this._probeSetAdded, this);
WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.ProbeSetRemoved, this._probeSetRemoved, this);
if (this._probeSet)
this._removeProbeSet(this._probeSet);
}
populateContextMenu(contextMenu, event)
{
WI.BreakpointPopover.appendContextMenuItems(contextMenu, this._breakpoint, this.status);
super.populateContextMenu(contextMenu, event);
}
// Protected
updateStatus()
{
if (!this.status)
return;
this.status.classList.toggle("resolved", this._breakpoint.resolved);
this.status.classList.toggle("disabled", this._breakpoint.disabled);
if (this._breakpoint.editable)
this.status.classList.toggle("auto-continue", this._breakpoint.autoContinue);
}
// Private
_addProbeSet(probeSet)
{
console.assert(probeSet instanceof WI.ProbeSet);
console.assert(probeSet.breakpoint === this._breakpoint);
console.assert(probeSet !== this._probeSet);
this._probeSet = probeSet;
probeSet.addEventListener(WI.ProbeSet.Event.SamplesCleared, this._samplesCleared, this);
probeSet.dataTable.addEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
}
_removeProbeSet(probeSet)
{
console.assert(probeSet instanceof WI.ProbeSet);
console.assert(probeSet === this._probeSet);
probeSet.removeEventListener(WI.ProbeSet.Event.SamplesCleared, this._samplesCleared, this);
probeSet.dataTable.removeEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
this._probeSet = null;
}
_probeSetAdded(event)
{
var probeSet = event.data.probeSet;
if (probeSet.breakpoint === this._breakpoint)
this._addProbeSet(probeSet);
}
_probeSetRemoved(event)
{
var probeSet = event.data.probeSet;
if (probeSet.breakpoint === this._breakpoint)
this._removeProbeSet(probeSet);
}
_samplesCleared(event)
{
console.assert(this._probeSet);
var oldTable = event.data.oldTable;
oldTable.removeEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
this._probeSet.dataTable.addEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
}
_dataUpdated()
{
if (this.element.classList.contains("data-updated")) {
clearTimeout(this._removeIconAnimationTimeoutIdentifier);
this.element.classList.remove("data-updated");
// We want to restart the animation, which can only be done by removing the class,
// performing layout, and re-adding the class. Try adding class back on next run loop.
window.requestAnimationFrame(this._dataUpdated.bind(this));
return;
}
this.element.classList.add("data-updated");
this._removeIconAnimationTimeoutIdentifier = setTimeout(() => {
this.element.classList.remove("data-updated");
}, WI.BreakpointTreeElement.ProbeDataUpdatedAnimationDuration);
}
_statusImageElementMouseDown(event)
{
// To prevent the tree element from selecting.
event.stopPropagation();
}
_statusImageElementClicked(event)
{
this._breakpoint.disabled = !this._breakpoint.disabled;
}
_handleStatusImageElementDoubleClicked(event)
{
WI.BreakpointPopover.show(this._breakpoint, this.status);
}
};
WI.BreakpointTreeElement.ProbeDataUpdatedAnimationDuration = 400; // milliseconds
+119
View File
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2013-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.
*/
.navigation-bar .item.button {
justify-content: center;
color: hsl(0, 0%, 18%);
}
.navigation-bar .item.button:not(.image-only):focus,
.navigation-bar .item.button.image-only:focus > .glyph {
outline: auto -webkit-focus-ring-color;
}
.navigation-bar .item.button.image-only:focus {
outline: none;
}
.navigation-bar .item.button.image-only {
width: var(--image-button-navigation-item-width);
}
.navigation-bar .item.button.image-and-text {
padding: 0 8px;
}
.navigation-bar .item.button.image-and-text > span {
padding-bottom: 1px;
padding-inline-start: 4px;
}
.tab-bar > .navigation-bar .item.button.image-and-text > span {
padding-bottom: 0;
}
.navigation-bar .item.button.text-only {
padding: 1px 4px 3px;
margin: 0 2px; /* WI.ButtonNavigationItem.prototype.get textMargin */
height: initial;
line-height: 11px;
border: 1px solid transparent;
border-radius: 3px;
text-align: center;
}
.navigation-bar .item.button.text-only:focus {
outline-offset: var(--focus-ring-outline-offset);
}
.navigation-bar .item.button > .glyph {
color: var(--glyph-color);
opacity: var(--glyph-opacity);
}
.navigation-bar .item.button:not(.disabled):active > .glyph {
color: var(--glyph-color-pressed);
}
.navigation-bar .item.button:not(.disabled):matches(.activate.activated, .radio.selected) > .glyph {
color: var(--glyph-color-active);
}
.navigation-bar .item.button:not(.disabled):active:matches(.activate.activated, .radio.selected) > .glyph {
color: var(--glyph-color-active-pressed);
}
.navigation-bar .item.button.disabled.image-and-text,
.navigation-bar .item.button.disabled > .glyph {
color: var(--glyph-color-disabled);
}
.navigation-bar .item.button > img {
width: 100%;
height: 100%;
}
.navigation-bar .item.button.disabled > img {
opacity: 0.3;
}
body.window-inactive .navigation-bar .item.button > img {
opacity: 0.6;
}
body.window-inactive .navigation-bar .item.button.disabled > img {
opacity: 0.18;
}
@media (prefers-color-scheme: dark) {
.navigation-bar .item.button {
color: var(--text-color-secondary);
}
.navigation-bar .item.button.disabled > img {
filter: invert();
}
}
+272
View File
@@ -0,0 +1,272 @@
/*
* Copyright (C) 2013, 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.ButtonNavigationItem = class ButtonNavigationItem extends WI.NavigationItem
{
constructor(identifier, toolTipOrLabel, image, imageWidth, imageHeight, role, label)
{
role = role || "button";
super(identifier, role);
console.assert(identifier);
console.assert(toolTipOrLabel);
this._enabled = true;
this.element.addEventListener("click", this._mouseClicked.bind(this));
// Don't move the focus on the button when clicking on it. This matches macOS behavior.
this.element.addEventListener("mousedown", this._handleMouseDown.bind(this), true);
this._role = role;
if (this._role === "button")
this.element.addEventListener("keydown", this._handleKeyDown.bind(this));
if (label)
this.element.setAttribute("aria-label", label);
this._buttonStyle = null;
this._disabled = false;
this._image = image || null;
this._imageWidth = imageWidth || 16;
this._imageHeight = imageHeight || 16;
this._label = toolTipOrLabel;
this._updateTabIndex();
this.buttonStyle = this._image ? WI.ButtonNavigationItem.Style.Image : WI.ButtonNavigationItem.Style.Text;
this.imageType = this._image ? WI.ButtonNavigationItem.ImageType.SVG : null;
}
// Public
get label()
{
return this._label;
}
set label(newLabel)
{
newLabel = newLabel || "";
if (this._label === newLabel)
return;
this._label = newLabel;
this._update();
if (this.parentNavigationBar)
this.parentNavigationBar.needsLayout();
}
get image()
{
return this._image;
}
set image(newImage)
{
newImage = newImage || null;
if (this._image === newImage)
return;
this._image = newImage;
this._update();
}
get enabled()
{
return this._enabled;
}
set enabled(flag)
{
flag = !!flag;
if (this._enabled === flag)
return;
this._enabled = flag;
this.element.classList.toggle("disabled", !this._enabled);
this.element.ariaDisabled = !this._enabled;
this._updateTabIndex();
}
get buttonStyle()
{
return this._buttonStyle;
}
set buttonStyle(newButtonStyle)
{
newButtonStyle = newButtonStyle || WI.ButtonNavigationItem.Style.Image;
if (this._buttonStyle === newButtonStyle)
return;
this.element.classList.remove(...Object.values(WI.ButtonNavigationItem.Style));
this.element.classList.add(newButtonStyle);
this._buttonStyle = newButtonStyle;
this._update();
if (this.parentNavigationBar)
this.parentNavigationBar.needsLayout();
}
get imageType()
{
return this._imageType;
}
set imageType(imageType)
{
console.assert(!imageType || Object.values(WI.ButtonNavigationItem.ImageType).includes(imageType), imageType);
console.assert(!imageType || (this._buttonStyle === WI.ButtonNavigationItem.Style.Image || this._buttonStyle === WI.ButtonNavigationItem.Style.ImageAndText));
if (this._imageType === imageType)
return;
this._imageType = imageType;
this._update();
if (this.parentNavigationBar)
this.parentNavigationBar.needsLayout();
}
// Protected
get totalMargin()
{
return super.totalMargin + this.textMargin;
}
get textMargin()
{
if (this._buttonStyle === ButtonNavigationItem.Style.Text)
return 4; /* .navigation-bar .item.button.text-only */
return 0;
}
get additionalClassNames()
{
return ["button"];
}
get tabbable()
{
return this._role === "button";
}
// Private
_mouseClicked(event)
{
this._buttonPressed(event);
}
_handleMouseDown(event)
{
// Clicking on a button should NOT focus on it.
event.preventDefault();
}
_handleKeyDown(event)
{
if (event.code === "Enter" || event.code === "Space") {
event.stop();
this._buttonPressed(event);
}
}
_buttonPressed(event)
{
if (!this.enabled)
return;
this.dispatchEventToListeners(WI.ButtonNavigationItem.Event.Clicked, {nativeEvent: event});
}
_update()
{
this.element.removeChildren();
if (this._buttonStyle === WI.ButtonNavigationItem.Style.Text)
this.element.textContent = this._label;
else {
switch (this._imageType) {
case null:
case WI.ButtonNavigationItem.ImageType.SVG: {
if (this._image) {
let glyphElement = WI.ImageUtilities.useSVGSymbol(this._image, "glyph");
glyphElement.style.width = this._imageWidth + "px";
glyphElement.style.height = this._imageHeight + "px";
this.element.appendChild(glyphElement);
}
break;
}
case WI.ButtonNavigationItem.ImageType.IMG: {
let img = this.element.appendChild(document.createElement("img"));
if (this._image)
img.src = this._image;
break;
}
}
if (this._buttonStyle === WI.ButtonNavigationItem.Style.ImageAndText) {
let labelElement = this.element.appendChild(document.createElement("span"));
labelElement.textContent = this._label;
}
}
if (this._buttonStyle === WI.ButtonNavigationItem.Style.Image)
this.tooltip ||= this._label;
}
_updateTabIndex()
{
if (!this._enabled) {
this.element.tabIndex = -1;
return;
}
this.element.tabIndex = this.tabbable ? 0 : -1;
}
};
WI.ButtonNavigationItem.Event = {
Clicked: "button-navigation-item-clicked"
};
WI.ButtonNavigationItem.Style = {
Image: "image-only",
Text: "text-only",
ImageAndText: "image-and-text",
};
WI.ButtonNavigationItem.ImageType = {
SVG: "image-type-svg",
IMG: "image-type-img",
};
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2013-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.
*/
.toolbar .item.button {
align-items: center;
justify-content: center;
margin: 4px;
padding: 0 10px;
color: hsl(0, 0%, 45%);
}
.toolbar .item.button:not(.disabled):active {
color: hsl(0, 0%, 30%);
}
.toolbar .item.button:not(.disabled):matches(:focus, .activate.activated) {
color: var(--glyph-color-active);
}
.toolbar .item.button > .glyph {
width: 16px;
height: 16px;
}
body.window-inactive .toolbar .item.button {
opacity: 0.65;
}
.toolbar .item.button.disabled {
opacity: 0.55 !important;
}
body.window-inactive .toolbar .item.button.disabled {
opacity: 0.35 !important;
}
@media (prefers-color-scheme: dark) {
.toolbar .item.button {
color: var(--color-button);
}
.toolbar .item.button:not(.disabled):active {
color: var(--color-button-active);
background-image: none;
background: var(--button-background-color-pressed);
}
body:not(.window-inactive) .toolbar .item.button:not(.disabled):matches(:focus, .activate.activated) > .glyph {
filter: brightness(1.35);
}
}
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2013, 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.ButtonToolbarItem = class ButtonToolbarItem extends WI.ButtonNavigationItem
{
constructor(identifier, toolTip, image, role)
{
super(identifier, toolTip, image, 16, 16, role);
}
};
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2019-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.
*/
body .sidebar > .panel.navigation.timeline > .timelines-content li.item.cpu,
body .timeline-overview > .graphs-container > .timeline-overview-graph.cpu {
height: 60px;
}
.timeline-overview-graph.cpu {
position: relative;
}
.timeline-overview-graph.cpu > .legend {
position: absolute;
top: 0;
inset-inline-end: 0;
z-index: var(--timeline-record-z-index);
padding: 2px;
font-size: 8px;
color: hsl(0, 0%, 50%);
background-color: var(--timeline-odd-background-color);
pointer-events: none;
}
.timeline-overview-graph.cpu:nth-child(even) > .legend {
background-color: var(--timeline-even-background-color);
}
body[dir=rtl] .timeline-overview-graph.cpu > .stacked-column-chart {
transform: scaleX(-1);
}
.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.total-usage {
fill: var(--cpu-other-thread-fill-color);
stroke: var(--cpu-other-thread-stroke-color);
}
.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.main-thread-usage {
fill: var(--cpu-main-thread-fill-color);
stroke: var(--cpu-main-thread-stroke-color);
}
.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.worker-thread-usage {
fill: var(--cpu-worker-thread-fill-color);
stroke: var(--cpu-worker-thread-stroke-color);
}
.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.selected {
fill: var(--selected-background-color) !important;
fill-opacity: 0.5;
stroke: var(--selected-background-color-active) !important;
stroke-opacity: 0.8;
}
+216
View File
@@ -0,0 +1,216 @@
/*
* 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.CPUTimelineOverviewGraph = class CPUTimelineOverviewGraph extends WI.TimelineOverviewGraph
{
constructor(timeline, timelineOverview)
{
console.assert(timeline instanceof WI.Timeline);
console.assert(timeline.type === WI.TimelineRecord.Type.CPU, timeline);
super(timelineOverview);
this.element.classList.add("cpu");
this._cpuTimeline = timeline;
this._cpuTimeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this);
let size = new WI.Size(0, this.height);
this._chart = new WI.StackedColumnChart(size);
this._chart.initializeSections(["main-thread-usage", "worker-thread-usage", "total-usage"]);
this.addSubview(this._chart);
this.element.appendChild(this._chart.element);
this._chart.element.addEventListener("click", this._handleChartClick.bind(this));
this._legendElement = this.element.appendChild(document.createElement("div"));
this._legendElement.classList.add("legend");
this._lastSelectedRecordInLayout = null;
this.reset();
for (let record of this._cpuTimeline.records)
this._processRecord(record);
}
// Protected
get height()
{
return 60;
}
reset()
{
super.reset();
this._maxUsage = 0;
this._cachedMaxUsage = undefined;
this._lastSelectedRecordInLayout = null;
this._updateLegend();
this._chart.clear();
this._chart.needsLayout();
}
layout()
{
super.layout();
if (this.hidden)
return;
this._updateLegend();
this._chart.clear();
let graphWidth = this.timelineOverview.scrollContainerWidth;
if (isNaN(graphWidth))
return;
this._lastSelectedRecordInLayout = this.selectedRecord;
if (this._chart.size.width !== graphWidth || this._chart.size.height !== this.height)
this._chart.size = new WI.Size(graphWidth, this.height);
let graphStartTime = this.startTime;
let visibleEndTime = Math.min(this.endTime, this.currentTime);
let secondsPerPixel = this.timelineOverview.secondsPerPixel;
let maxCapacity = Math.max(20, this._maxUsage * 1.05); // Add 5% for padding.
function xScale(time) {
return (time - graphStartTime) / secondsPerPixel;
}
let height = this.height;
function yScale(size) {
return (size / maxCapacity) * height;
}
let visibleRecords = this._cpuTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, {
includeRecordBeforeStart: true,
});
if (!visibleRecords.length)
return;
const minimumDisplayHeight = 4;
for (let record of visibleRecords) {
let additionalClass = record === this.selectedRecord ? "selected" : undefined;
let w = (record.endTime - record.startTime) / secondsPerPixel;
let x = xScale(record.startTime);
let h1 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage));
let h2 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage + record.workerThreadUsage));
let h3 = Math.max(minimumDisplayHeight, yScale(record.usage));
this._chart.addColumnSet(x, height, w, [h1, h2, h3], additionalClass);
}
}
updateSelectedRecord()
{
super.updateSelectedRecord();
if (this._lastSelectedRecordInLayout !== this.selectedRecord) {
// Since we don't have the exact element to re-style with a selected appearance
// we trigger another layout to re-layout the graph and provide additional
// styles for the column for the selected record.
this.needsLayout();
}
}
// Private
_updateLegend()
{
if (this._cachedMaxUsage === this._maxUsage)
return;
this._cachedMaxUsage = this._maxUsage;
if (!this._maxUsage) {
this._legendElement.hidden = true;
this._legendElement.textContent = "";
} else {
this._legendElement.hidden = false;
this._legendElement.textContent = WI.UIString("Maximum CPU Usage: %s").format(Number.percentageString(this._maxUsage / 100));
}
}
_graphPositionForMouseEvent(event)
{
// Only trigger if clicking on a rect, not anywhere in the graph.
let elements = document.elementsFromPoint(event.pageX, event.pageY);
let rectElement = elements.find((x) => x.localName === "rect");
if (!rectElement)
return NaN;
let chartElement = rectElement.closest(".stacked-column-chart");
if (!chartElement)
return NaN;
let rect = chartElement.getBoundingClientRect();
let position = event.pageX - rect.left;
if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL)
return rect.width - position;
return position;
}
_handleChartClick(event)
{
let position = this._graphPositionForMouseEvent(event);
if (isNaN(position))
return;
let secondsPerPixel = this.timelineOverview.secondsPerPixel;
let graphClickTime = position * secondsPerPixel;
let graphStartTime = this.startTime;
let clickTime = graphStartTime + graphClickTime;
let record = this._cpuTimeline.closestRecordTo(clickTime);
if (!record)
return;
// Ensure that the container "click" listener added by `WI.TimelineOverview` isn't called.
event.__timelineRecordClickEventHandled = true;
this.selectedRecord = record;
this.needsLayout();
}
_cpuTimelineRecordAdded(event)
{
let cpuTimelineRecord = event.data.record;
this._processRecord(cpuTimelineRecord);
this.needsLayout();
}
_processRecord(cpuTimelineRecord)
{
this._maxUsage = Math.max(this._maxUsage, cpuTimelineRecord.usage);
}
};
+364
View File
@@ -0,0 +1,364 @@
/*
* Copyright (C) 2019-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.
*/
.timeline-view.cpu {
overflow: auto !important;
}
.timeline-view.cpu > .content .subtitle {
font-family: -webkit-system-font, sans-serif;
font-size: 14px;
}
.timeline-view.cpu > .content .subtitle > .info {
display: inline-block;
width: 15px;
height: 15px;
margin-inline-start: 7px;
font-size: 12px;
color: var(--gray-foreground-color);
background-color: var(--gray-background-color);
border-radius: 50%;
}
.energy-info-popover-content {
width: 275px;
padding: 0 5px;
-webkit-hyphens: auto;
}
.timeline-view.cpu > .content > .details {
position: relative;
}
.timeline-view.cpu > .content > .details > .timeline-ruler {
position: absolute;
top: 5px;
bottom: 0;
right: 0;
left: 0;
inset-inline: 150px 0;
}
.timeline-view.cpu > .content > .details > .subtitle,
.timeline-view.cpu > .content > .details > details > .subtitle {
padding: 0 10px 10px;
border-bottom: 1px solid var(--border-color);
}
.timeline-view.cpu > .content > .details > details > .subtitle.threads {
position: relative;
z-index: calc(var(--timeline-marker-z-index) + 1);
padding-top: 10px;
background-color: var(--background-color-content);
}
.timeline-view.cpu > .content > .overview {
display: flex;
justify-content: center;
padding: 10px;
}
.timeline-view.cpu > .content > .overview:not(:last-child) {
margin-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.timeline-view.cpu > .content > .overview > .chart {
width: 420px;
text-align: center;
}
.timeline-view.cpu > .content > .overview > .chart > .subtitle {
margin-bottom: 1em;
}
.timeline-view.cpu > .content > .overview > .chart > .container {
display: flex;
justify-content: center;
}
.timeline-view.cpu > .content > .overview > .divider {
margin: 0 5px;
border-inline-end: 1px solid var(--border-color);
}
.timeline-view.cpu > .content > .overview .samples,
.timeline-view.cpu > .content > .overview .legend .size {
margin: auto;
color: var(--text-color-secondary);
}
.timeline-view.cpu > .content > .overview .legend {
padding-inline-start: 20px;
text-align: start;
-webkit-user-select: text;
}
.timeline-view.cpu > .content > .overview .legend .row {
display: flex;
}
.timeline-view.cpu > .content > .overview .legend .row + .row {
margin-top: 4px;
}
.timeline-view.cpu > .content > .overview .legend .swatch {
width: 1em;
height: 1em;
margin-top: 1px;
margin-inline-end: 8px;
}
.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-javascript {
border: 1px solid var(--cpu-javascript-stroke-color);
background-color: var(--cpu-javascript-fill-color);
}
.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-style {
border: 1px solid var(--cpu-style-stroke-color);
background-color: var(--cpu-style-fill-color);
}
.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-layout {
border: 1px solid var(--cpu-layout-stroke-color);
background-color: var(--cpu-layout-fill-color);
}
.timeline-view.cpu > .content > .overview .legend > .row > .swatch.sample-type-paint {
border: 1px solid var(--cpu-paint-stroke-color);
background-color: var(--cpu-paint-fill-color);
}
.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-idle {
stroke: var(--cpu-idle-stroke-color);
fill: var(--cpu-idle-fill-color);
}
.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-javascript {
stroke: var(--cpu-javascript-stroke-color);
fill: var(--cpu-javascript-fill-color);
}
.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-style {
stroke: var(--cpu-style-stroke-color);
fill: var(--cpu-style-fill-color);
}
.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-layout {
stroke: var(--cpu-layout-stroke-color);
fill: var(--cpu-layout-fill-color);
}
.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-paint {
stroke: var(--cpu-paint-stroke-color);
fill: var(--cpu-paint-fill-color);
}
.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) svg > path {
fill: var(--cpu-other-thread-fill-color);
stroke: var(--cpu-other-thread-stroke-color);
}
.timeline-view.cpu .main-thread svg > path,
.timeline-view.cpu svg > path.main-thread-usage {
fill: var(--cpu-main-thread-fill-color);
stroke: var(--cpu-main-thread-stroke-color);
}
.timeline-view.cpu .worker-thread svg > path,
.timeline-view.cpu svg > path.worker-thread-usage {
fill: var(--cpu-worker-thread-fill-color);
stroke: var(--cpu-worker-thread-stroke-color);
}
.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) .markers {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) .markers > div {
position: absolute;
z-index: 10;
width: 100%;
height: 1px;
text-align: end;
background-color: var(--graph-line-color);
}
body[dir=rtl] .timeline-view.cpu :matches(.area-chart, .stacked-area-chart) .markers > div {
transform: scaleX(-1);
}
.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) .markers > div > .label {
padding: 2px;
font-size: 8px;
color: var(--text-color-secondary);
background-color: var(--background-color-content);
}
.timeline-view.cpu :matches(.area-chart, .stacked-area-chart) circle {
r: 3;
fill: var(--cpu-overlay-color);
}
.timeline-view.cpu .timeline-ruler > .markers > .marker.timestamp {
color: var(--cpu-overlay-color);
opacity: 0.8;
pointer-events: none;
}
.timeline-view.cpu .timeline-ruler > .markers > .marker.timestamp::after {
display: none;
}
.timeline-view.cpu .gauge-chart .low {
--gauge-chart-path-fill-color: var(--cpu-low-color);
--gauge-chart-path-stroke-color: var(--cpu-low-color);
}
.timeline-view.cpu .gauge-chart .medium {
--gauge-chart-path-fill-color: var(--cpu-medium-color);
--gauge-chart-path-stroke-color: var(--cpu-medium-color);
}
.timeline-view.cpu .gauge-chart .high {
--gauge-chart-path-fill-color: var(--cpu-high-color);
--gauge-chart-path-stroke-color: var(--cpu-high-color);
}
.timeline-view.cpu .gauge-chart {
--gauge-chart-needle-fill-color: hsla(0, 0%, 36%, 0.85);
--gauge-chart-needle-stroke-color: hsla(0, 0%, 36%, 0.85);
}
.timeline-view.cpu .energy {
color: hsla(0, 0%, var(--foreground-lightness), 0.85);
}
.timeline-view.cpu .energy .energy-impact {
min-width: 140px;
margin-top: 15px;
font-size: 3em;
color: var(--text-color-secondary);
}
.timeline-view.cpu .energy .energy-impact.low {
color: var(--cpu-low-color);
}
.timeline-view.cpu .energy .energy-impact.medium {
color: var(--cpu-medium-color);
}
.timeline-view.cpu .energy .energy-impact.high {
color: var(--cpu-high-color);
}
.timeline-view.cpu .energy .energy-impact-number {
font-size: 14px;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats {
padding: 0 5px;
white-space: nowrap;
-webkit-user-select: text;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table {
overflow: hidden;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > th {
text-align: end;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.number {
min-width: 25px;
padding: 0px 2px;
text-align: end;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.label {
text-align: start;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .show-more {
cursor: pointer;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .unknown {
color: var(--link-text-color);
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter-clear {
display: inline-block;
width: 13px;
height: 13px;
font-size: 12px;
color: var(--gray-foreground-color);
background-color: var(--gray-background-color);
border-radius: 50%;
line-height: 12px;
text-align: center;
cursor: pointer;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter {
padding: 0 6px 1px;
font-size: 10px;
background-color: hsl(0, 0%, 85%);
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table :matches(.filter, .filter-clear):hover {
opacity: 0.7;
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter.active {
color: var(--selected-foreground-color);
background-color: var(--selected-background-color);
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter.active + .filter.active {
margin-inline-start: 3px;
}
@media (prefers-color-scheme: dark) {
.timeline-view.cpu .gauge-chart:not(.empty) > svg > polygon.needle {
fill: hsla(0, 0%, var(--foreground-lightness), 0.85);
stroke: hsla(0, 0%, var(--foreground-lightness), 0.85);
}
.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter {
background-color: hsl(0, 0%, 33%);
}
}
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
/*
* Copyright (C) 2019-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.
*/
.cpu-usage-combined-view {
display: flex;
width: 100%;
height: calc(var(--cpu-usage-combined-view-height) + var(--cpu-usage-indicator-view-height) + 2px); /* +2 for borders */
border-bottom: 1px solid var(--border-color);
}
.cpu-usage-combined-view > .graph,
.cpu-usage-combined-view > .graph > :matches(.stacked-area-chart, .range-chart),
.cpu-usage-combined-view > .graph > :matches(.stacked-area-chart, .range-chart) > svg {
width: 100%;
height: 100%;
}
.cpu-usage-combined-view > .graph > .stacked-area-chart {
height: var(--cpu-usage-combined-view-height);
}
.cpu-usage-combined-view > .graph > .range-chart {
position: relative;
z-index: calc(var(--timeline-marker-z-index) + 1);
height: calc(var(--cpu-usage-indicator-view-height) + 2px); /* +2 for borders */
background-color: var(--background-color-content);
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
}
.cpu-usage-combined-view > .details {
flex-shrink: 0;
width: 150px;
padding-top: 10px;
padding-inline-start: 15px;
border-inline-end: 1px solid var(--border-color);
font-size: 12px;
color: var(--text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
-webkit-user-select: text;
}
.cpu-usage-combined-view > :matches(.details, .legend) > .name {
color: var(--text-color);
white-space: nowrap;
}
.cpu-usage-combined-view > .graph {
position: relative;
}
body[dir=rtl] .cpu-usage-combined-view > .graph {
transform: scaleX(-1);
}
.cpu-usage-combined-view > .details > .legend-container {
font-size: 11px;
}
.cpu-usage-combined-view > .details > .legend-container > .row {
display: flex;
}
.cpu-usage-combined-view > .details > .legend-container > .row + .row {
margin-top: 4px;
}
.cpu-usage-combined-view > .details > .legend-container > .row > .swatch {
width: 1em;
height: 1em;
margin-top: 1px;
margin-inline-end: 4px;
}
.cpu-usage-combined-view > .details > .legend-container .swatch.total {
background-color: none;
border: none;
}
.cpu-usage-combined-view > .details > .legend-container .swatch.other-threads {
background-color: var(--cpu-other-thread-fill-color);
border: 1px solid var(--cpu-other-thread-stroke-color);
}
.cpu-usage-combined-view > .details > .legend-container .swatch.main-thread {
background-color: var(--cpu-main-thread-fill-color);
border: 1px solid var(--cpu-main-thread-stroke-color);
}
.cpu-usage-combined-view > .details > .legend-container .swatch.worker-threads {
background-color: var(--cpu-worker-thread-fill-color);
border: 1px solid var(--cpu-worker-thread-stroke-color);
}
.cpu-usage-combined-view > .graph > .range-chart rect {
stroke-opacity: 0.25;
}
.cpu-usage-combined-view > .graph > .range-chart .sample-type-javascript {
stroke: var(--cpu-javascript-stroke-color);
fill: var(--cpu-javascript-fill-color);
}
.cpu-usage-combined-view > .graph > .range-chart .sample-type-style {
stroke: var(--cpu-style-stroke-color);
fill: var(--cpu-style-fill-color);
}
.cpu-usage-combined-view > .graph > .range-chart .sample-type-layout {
stroke: var(--cpu-layout-stroke-color);
fill: var(--cpu-layout-fill-color);
}
.cpu-usage-combined-view > .graph > .range-chart .sample-type-paint {
stroke: var(--cpu-paint-stroke-color);
fill: var(--cpu-paint-fill-color);
}
+245
View File
@@ -0,0 +1,245 @@
/*
* 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.CPUUsageCombinedView = class CPUUsageCombinedView extends WI.View
{
constructor(displayName)
{
super();
this.element.classList.add("cpu-usage-combined-view");
this._detailsElement = this.element.appendChild(document.createElement("div"));
this._detailsElement.classList.add("details");
let detailsNameElement = this._detailsElement.appendChild(document.createElement("span"));
detailsNameElement.classList.add("name");
detailsNameElement.textContent = displayName;
this._detailsElement.appendChild(document.createElement("br"));
this._detailsAverageElement = this._detailsElement.appendChild(document.createElement("span"));
this._detailsElement.appendChild(document.createElement("br"));
this._detailsMaxElement = this._detailsElement.appendChild(document.createElement("span"));
this._detailsElement.appendChild(document.createElement("br"));
this._detailsElement.appendChild(document.createElement("br"));
this._updateDetails(NaN, NaN);
this._graphElement = this.element.appendChild(document.createElement("div"));
this._graphElement.classList.add("graph");
// Combined thread usage area chart.
this._chart = new WI.StackedAreaChart;
this._chart.initializeSections(["main-thread-usage", "worker-thread-usage", "total-usage"]);
this.addSubview(this._chart);
this._graphElement.appendChild(this._chart.element);
// Main thread indicator strip.
this._rangeChart = new WI.RangeChart;
this.addSubview(this._rangeChart);
this._graphElement.appendChild(this._rangeChart.element);
function appendLegendRow(legendElement, className) {
let rowElement = legendElement.appendChild(document.createElement("div"));
rowElement.classList.add("row");
let swatchElement = rowElement.appendChild(document.createElement("div"));
swatchElement.classList.add("swatch", className);
let labelElement = rowElement.appendChild(document.createElement("div"));
labelElement.classList.add("label");
return labelElement;
}
this._legendElement = this._detailsElement.appendChild(document.createElement("div"));
this._legendElement.classList.add("legend-container");
this._legendMainThreadElement = appendLegendRow(this._legendElement, "main-thread");
this._legendWorkerThreadsElement = appendLegendRow(this._legendElement, "worker-threads");
this._legendOtherThreadsElement = appendLegendRow(this._legendElement, "other-threads");
this._legendTotalThreadsElement = appendLegendRow(this._legendElement, "total");
this.clearLegend();
}
// Public
get graphElement() { return this._graphElement; }
get chart() { return this._chart; }
get rangeChart() { return this._rangeChart; }
clear()
{
this._cachedAverageSize = undefined;
this._cachedMaxSize = undefined;
this._updateDetails(NaN, NaN);
this.clearLegend();
this._chart.clear();
this._chart.needsLayout();
this._rangeChart.clear();
this._rangeChart.needsLayout();
}
updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale)
{
console.assert(size instanceof WI.Size);
console.assert(min >= 0);
console.assert(max >= 0);
console.assert(min <= max);
console.assert(min <= average && average <= max);
this._updateDetails(max, average);
this._chart.clearPoints();
this._chart.size = size;
this._chart.needsLayout();
if (!dataPoints.length)
return;
// Ensure an empty graph is empty.
if (!max)
return;
// Extend the first data point to the start so it doesn't look like we originate at zero size.
let firstX = 0;
let firstY1 = yScale(dataPoints[0].mainThreadUsage);
let firstY2 = yScale(dataPoints[0].mainThreadUsage + dataPoints[0].workerThreadUsage);
let firstY3 = yScale(dataPoints[0].usage);
this._chart.addPointSet(firstX, [firstY1, firstY2, firstY3]);
// Points for data points.
for (let dataPoint of dataPoints) {
let x = xScale(dataPoint.time);
let y1 = yScale(dataPoint.mainThreadUsage);
let y2 = yScale(dataPoint.mainThreadUsage + dataPoint.workerThreadUsage);
let y3 = yScale(dataPoint.usage);
this._chart.addPointSet(x, [y1, y2, y3]);
}
// Extend the last data point to the end time.
let lastDataPoint = dataPoints.lastValue;
let lastX = Math.floor(xScale(visibleEndTime));
let lastY1 = yScale(lastDataPoint.mainThreadUsage);
let lastY2 = yScale(lastDataPoint.mainThreadUsage + lastDataPoint.workerThreadUsage);
let lastY3 = yScale(lastDataPoint.usage);
this._chart.addPointSet(lastX, [lastY1, lastY2, lastY3]);
}
updateMainThreadIndicator(samples, size, visibleEndTime, xScale)
{
console.assert(size instanceof WI.Size);
this._rangeChart.clear();
this._rangeChart.size = size;
this._rangeChart.needsLayout();
if (!samples.length)
return;
// Coalesce ranges of samples.
let ranges = [];
let currentRange = null;
let currentSampleType = undefined;
for (let i = 0; i < samples.length; ++i) {
// Back to idle, close any current chunk.
let type = samples[i];
if (!type) {
if (currentRange) {
ranges.push(currentRange);
currentRange = null;
currentSampleType = undefined;
}
continue;
}
// Expand existing chunk.
if (type === currentSampleType) {
currentRange.endIndex = i;
continue;
}
// If type changed, close current chunk.
if (currentSampleType) {
ranges.push(currentRange);
currentRange = null;
currentSampleType = undefined;
}
// Start a new chunk.
console.assert(!currentRange);
console.assert(!currentSampleType);
currentRange = {type, startIndex: i, endIndex: i};
currentSampleType = type;
}
for (let {type, startIndex, endIndex} of ranges) {
let startX = xScale(startIndex);
let endX = xScale(endIndex + 1);
let width = endX - startX;
this._rangeChart.addRange(startX, width, type);
}
}
clearLegend()
{
this._legendMainThreadElement.textContent = WI.UIString("Main Thread");
this._legendWorkerThreadsElement.textContent = WI.UIString("Worker Threads");
this._legendOtherThreadsElement.textContent = WI.UIString("Other Threads");
this._legendTotalThreadsElement.textContent = "";
}
updateLegend(record)
{
if (!record) {
this.clearLegend();
return;
}
let {usage, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage} = record;
this._legendMainThreadElement.textContent = WI.UIString("Main: %s").format(Number.percentageString(mainThreadUsage / 100));
this._legendWorkerThreadsElement.textContent = WI.UIString("Worker: %s").format(Number.percentageString(workerThreadUsage / 100));
this._legendOtherThreadsElement.textContent = WI.UIString("Other: %s").format(Number.percentageString((webkitThreadUsage + unknownThreadUsage) / 100));
this._legendTotalThreadsElement.textContent = WI.UIString("Total: %s").format(Number.percentageString(usage / 100));
}
// Private
_updateDetails(maxSize, averageSize)
{
if (this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize)
return;
this._cachedAverageSize = averageSize;
this._cachedMaxSize = maxSize;
this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(maxSize) ? Number.percentageString(averageSize / 100) : emDash);
this._detailsMaxElement.textContent = WI.UIString("Highest: %s").format(Number.isFinite(maxSize) ? Number.percentageString(maxSize / 100) : emDash);
}
};
+64
View File
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2019-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.
*/
.cpu-usage-view {
display: flex;
width: 100%;
height: calc(var(--cpu-usage-view-height) + 1px); /* +1 for border-bottom */
border-bottom: 1px solid var(--border-color);
}
.cpu-usage-view > .details {
flex-shrink: 0;
width: 150px;
padding-top: 10px;
padding-inline-start: 15px;
border-inline-end: 1px solid var(--border-color);
font-family: -webkit-system-font, sans-serif;
font-size: 12px;
color: var(--text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
}
.cpu-usage-view > .details > .name {
color: var(--text-color);
white-space: nowrap;
}
body[dir=rtl] .cpu-usage-view > .graph {
transform: scaleX(-1);
}
.cpu-usage-view > .graph {
position: relative;
}
.cpu-usage-view > .graph,
.cpu-usage-view > .graph > .area-chart,
.cpu-usage-view > .graph > .area-chart > svg {
width: 100%;
height: 100%;
}
+144
View File
@@ -0,0 +1,144 @@
/*
* 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.CPUUsageView = class CPUUsageView extends WI.View
{
constructor(displayName)
{
super();
this.element.classList.add("cpu-usage-view");
this._detailsElement = this.element.appendChild(document.createElement("div"));
this._detailsElement.classList.add("details");
if (displayName) {
let detailsNameElement = this._detailsElement.appendChild(document.createElement("span"));
detailsNameElement.classList.add("name");
detailsNameElement.textContent = displayName;
if (displayName.length >= 20)
detailsNameElement.title = displayName;
this._detailsElement.appendChild(document.createElement("br"));
}
this._detailsAverageElement = this._detailsElement.appendChild(document.createElement("span"));
this._detailsElement.appendChild(document.createElement("br"));
this._detailsUsageElement = this._detailsElement.appendChild(document.createElement("span"));
this.clearLegend();
this._updateDetails(NaN);
this._graphElement = this.element.appendChild(document.createElement("div"));
this._graphElement.classList.add("graph");
this._chart = new WI.AreaChart;
this.addSubview(this._chart);
this._graphElement.appendChild(this._chart.element);
}
// Public
get graphElement() { return this._graphElement; }
get chart() { return this._chart; }
clear()
{
this._cachedAverageSize = undefined;
this._updateDetails(NaN);
this.clearLegend();
this._chart.clear();
this._chart.needsLayout();
}
updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale, property)
{
console.assert(size instanceof WI.Size);
console.assert(min >= 0);
console.assert(max >= 0);
console.assert(min <= max);
console.assert(min <= average && average <= max);
console.assert(property, "CPUUsageView needs a property of the dataPoints to graph");
this._updateDetails(average);
this._chart.clearPoints();
this._chart.size = size;
this._chart.needsLayout();
if (!dataPoints.length)
return;
// Ensure an empty graph is empty.
if (!max)
return;
// Extend the first data point to the start so it doesn't look like we originate at zero size.
let firstX = 0;
let firstY = yScale(dataPoints[0][property]);
this._chart.addPoint(firstX, firstY);
// Points for data points.
for (let dataPoint of dataPoints) {
let x = xScale(dataPoint.time);
let y = yScale(dataPoint[property]);
this._chart.addPoint(x, y);
}
// Extend the last data point to the end time.
let lastDataPoint = dataPoints.lastValue;
let lastX = Math.floor(xScale(visibleEndTime));
let lastY = yScale(lastDataPoint[property]);
this._chart.addPoint(lastX, lastY);
}
clearLegend()
{
this._detailsUsageElement.hidden = true;
this._detailsUsageElement.textContent = emDash;
}
updateLegend(value)
{
let usage = Number.isFinite(value) ? Number.percentageString(value / 100) : emDash;
this._detailsUsageElement.hidden = false;
this._detailsUsageElement.textContent = WI.UIString("Usage: %s").format(usage);
}
// Private
_updateDetails(averageSize)
{
if (this._cachedAverageSize === averageSize)
return;
this._cachedAverageSize = averageSize;
this._detailsAverageElement.hidden = !Number.isFinite(averageSize);
this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(averageSize) ? Number.percentageString(averageSize / 100) : emDash);
}
};
@@ -0,0 +1,53 @@
/*
* 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.
*/
.popover .documentation-popover-content {
max-width: 400px;
color: var(--text-color);
padding: 5px;
}
.popover .documentation-popover-content > p {
margin-bottom: 5px;
margin-top: 0px;
}
.popover .documentation-popover-content > .name-header {
font-weight: bold;
}
.popover .documentation-popover-content > .syntax {
font-family: Menlo, monospace;
font-size: 10.2px; /* This most closely matches the sizing of other details in popover, since syntax uses a different font-family. */
}
.popover .documentation-popover-content > .syntax > .syntax-title {
color: var(--syntax-highlight-boolean-color);
}
.popover .documentation-popover-content > .reference-link {
display: block;
color: var(--text-color-secondary);
}
+117
View File
@@ -0,0 +1,117 @@
/*
* 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.CSSDocumentationPopover = class CSSDocumentationPopover extends WI.Popover
{
constructor(cssProperty, delegate)
{
console.assert(cssProperty instanceof WI.CSSProperty, cssProperty);
super(delegate);
this._cssProperty = cssProperty;
this._targetElement = null;
this.windowResizeHandler = this._presentOverTargetElement.bind(this);
}
// Public
show(targetElement)
{
this._targetElement = targetElement;
let documentationDetails = this._getDocumentationDetails(this._cssProperty);
let documentationElement = this._createDocumentationElement(documentationDetails);
this.content = documentationElement;
this._presentOverTargetElement();
}
// Private
_presentOverTargetElement()
{
if (!this._targetElement)
return;
let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
this.present(targetFrame.pad(2), [WI.RectEdge.MIN_X, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_Y]);
}
_getDocumentationDetails(property)
{
let propertyName = "";
if (property.canonicalName in CSSDocumentation)
propertyName = property.canonicalName;
else if (property.name in CSSDocumentation)
propertyName = property.name;
let propertyDocumentation = CSSDocumentation[propertyName];
console.assert(propertyDocumentation, propertyName, CSSDocumentation);
return {
name: propertyName,
description: propertyDocumentation.description,
syntax: propertyDocumentation.syntax,
url: propertyDocumentation.url,
};
}
_createDocumentationElement(details)
{
let documentationElement = document.createElement("div");
documentationElement.className = "documentation-popover-content";
let nameElement = documentationElement.appendChild(document.createElement("p"));
nameElement.className = "name-header";
nameElement.textContent = details.name;
let descriptionElement = documentationElement.appendChild(document.createElement("p"));
descriptionElement.textContent = details.description;
if (details.syntax && WI.settings.showCSSPropertySyntaxInDocumentationPopover.value) {
let syntaxElement = documentationElement.appendChild(document.createElement("p"));
syntaxElement.className = "syntax";
let syntaxTitleElement = syntaxElement.appendChild(document.createElement("span"));
syntaxTitleElement.className = "syntax-title";
syntaxTitleElement.textContent = details.name;
let syntaxBodyElement = syntaxElement.appendChild(document.createElement("span"));
syntaxBodyElement.textContent = `: ${details.syntax}`;
}
if (details.url) {
let referenceLinkElement = documentationElement.appendChild(document.createElement("a"));
referenceLinkElement.className = "reference-link";
referenceLinkElement.textContent = WI.unlocalizedString("MDN Reference");
referenceLinkElement.href = details.url;
}
return documentationElement;
}
};
@@ -0,0 +1,50 @@
/*
* 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.CSSStyleSheetTreeElement = class CSSStyleSheetTreeElement extends WI.SourceCodeTreeElement
{
constructor(styleSheet)
{
console.assert(styleSheet instanceof WI.CSSStyleSheet);
const title = null;
const subtitle = null;
super(styleSheet, ["style-sheet", "style-sheet-icon"], title, subtitle);
this.mainTitle = styleSheet.displayName;
if (styleSheet.url) {
if (styleSheet.urlComponents.scheme === "web-inspector")
this.tooltip = this.mainTitle;
else {
// Show the host as the subtitle if it is different from the main title.
let host = WI.displayNameForHost(styleSheet.urlComponents.host);
this.subtitle = this.mainTitle !== host ? host : null;
this.tooltip = styleSheet.url;
}
}
}
};
+66
View File
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2013-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.
*/
.program-icon .icon {
content: url(../Images/TypeIcons.svg#Program-light);
}
.function-icon .icon {
content: url(../Images/TypeIcons.svg#Function-light);
}
.event-listener-icon .icon {
content: url(../Images/TypeIcons.svg#EventListener-light);
}
.native-icon .icon {
content: url(../Images/TypeIcons.svg#Native-light);
}
.tail-deleted .icon {
content: url(../Images/TypeIcons.svg#TailDeletedFunction-light);
}
@media (prefers-color-scheme: dark) {
.program-icon .icon {
content: url(../Images/TypeIcons.svg#Program-dark);
}
.function-icon .icon {
content: url(../Images/TypeIcons.svg#Function-dark);
}
.event-listener-icon .icon {
content: url(../Images/TypeIcons.svg#EventListener-dark);
}
.native-icon .icon {
content: url(../Images/TypeIcons.svg#Native-dark);
}
.tail-deleted .icon {
content: url(../Images/TypeIcons.svg#TailDeletedFunction-dark);
}
}
+106
View File
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2016-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.
*/
.tree-outline .item.call-frame {
position: relative;
}
.tree-outline .item.call-frame .status {
position: absolute;
inset-inline-start: var(--call-frame-status-start);
float: none;
margin-top: 0;
margin-inline-start: 1px;
--call-frame-status-start: 1px;
}
.tree-outline.single-thread .item.call-frame .status {
--call-frame-status-start: 11px;
}
body[dir=rtl] .tree-outline .item.call-frame .status {
transform: scaleX(-1);
}
.tree-outline .item.call-frame .status > .status-image {
width: 17px;
height: 17px;
fill: hsl(0, 0%, 70%);
}
.tree-outline .item.call-frame.selected .status > .status-image {
fill: hsl(0, 0%, 57%);
}
body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.call-frame.selected .status > .status-image {
fill: var(--selected-foreground-color);
}
.call-frame:is(.async-boundary, .truncated-boundary) {
cursor: default;
color: var(--text-color-gray-medium);
}
.tree-outline:not(.single-thread) > .children > .item.call-frame.async-boundary {
padding-inline-start: 5px;
}
.tree-outline .item.call-frame.async-boundary .icon {
display: inline-block;
float: none;
margin-inline-start: 0 !important;
}
.tree-outline .item.call-frame.async-boundary::before,
.tree-outline .item.call-frame.async-boundary::after {
content: "";
display: inline-block;
height: 0;
margin-bottom: 4px;
border-bottom: solid 0.5px var(--border-color);
}
.tree-outline .item.call-frame.async-boundary::after {
width: 100%;
margin-inline-start: 2px;
}
.tree-outline .item.call-frame.async-boundary::before {
width: 4px;
margin-inline: -5px 1px;
}
.tree-outline .children .item.call-frame.async-boundary::before {
width: 30px;
}
.tree-outline.single-thread .children .item.call-frame.async-boundary::before {
width: 20px;
}
.tree-outline .item.call-frame.blackboxed:not(.selected) {
opacity: var(--blackboxed-opacity);
}
+139
View File
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2013, 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.CallFrameTreeElement = class CallFrameTreeElement extends WI.GeneralTreeElement
{
constructor(callFrame, {isAsyncBoundaryCallFrame, isTruncatedBoundaryCallFrame} = {})
{
console.assert(callFrame instanceof WI.CallFrame);
let className = WI.CallFrameView.iconClassNameForCallFrame(callFrame);
let title = callFrame.displayName;
const subtitle = null;
super(["call-frame", className], title, subtitle, callFrame);
this._callFrame = callFrame;
this._isActiveCallFrame = false;
this._isAsyncBoundaryCallFrame = isAsyncBoundaryCallFrame || false;
if (this._isAsyncBoundaryCallFrame) {
this.addClassName("async-boundary");
this.selectable = false;
}
this._isTruncatedBoundaryCallFrame = !!isTruncatedBoundaryCallFrame;
if (this._isTruncatedBoundaryCallFrame) {
this.addClassName("truncated-boundary");
this.selectable = false;
}
if (this._callFrame.nativeCode || !this._callFrame.sourceCodeLocation) {
this.subtitle = "";
this.selectable = false;
return;
}
let displayScriptURL = this._callFrame.sourceCodeLocation.displaySourceCode.url;
if (displayScriptURL) {
this.subtitle = document.createElement("span");
this._callFrame.sourceCodeLocation.populateLiveDisplayLocationString(this.subtitle, "textContent");
// Set the tooltip on the entire tree element in onattach, once the element is created.
this.tooltipHandledSeparately = true;
}
if (this._callFrame.blackboxed) {
this.addClassName("blackboxed");
this.tooltipHandledSeparately = true;
}
}
// Public
get callFrame() { return this._callFrame; }
get isAsyncBoundaryCallFrame() { return this._isAsyncBoundaryCallFrame; }
get isActiveCallFrame()
{
return this._isActiveCallFrame;
}
set isActiveCallFrame(x)
{
if (this._isActiveCallFrame === x)
return;
this._isActiveCallFrame = x;
this._updateStatus();
}
// Protected
onattach()
{
super.onattach();
console.assert(this.element);
if (this.tooltipHandledSeparately) {
let tailCallSuffix = "";
if (this._callFrame.isTailDeleted)
tailCallSuffix = " " + WI.UIString("(Tail Call)");
let tooltipPrefix = this.mainTitle + tailCallSuffix + "\n";
let tooltipSuffix = "";
if (this._callFrame.blackboxed)
tooltipSuffix += "\n\n" + WI.UIString("Script ignored due to blackbox");
this._callFrame.sourceCodeLocation.populateLiveDisplayLocationTooltip(this.element, tooltipPrefix, tooltipSuffix);
}
this._updateStatus();
}
populateContextMenu(contextMenu, event)
{
if (this._callFrame.sourceCodeLocation)
WI.appendContextMenuItemsForSourceCode(contextMenu, this._callFrame.sourceCodeLocation);
super.populateContextMenu(contextMenu, event);
}
// Private
_updateStatus()
{
if (!this.element)
return;
if (!this._isActiveCallFrame) {
this.status = null;
return;
}
if (!this._statusImageElement)
this._statusImageElement = WI.ImageUtilities.useSVGSymbol("Images/ActiveCallFrame.svg", "status-image");
this.status = this._statusImageElement;
}
};
+92
View File
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2013-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.
*/
.call-frame {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.call-frame.blackboxed > .title,
.call-frame.blackboxed:not(:hover, :focus) > .subtitle {
opacity: var(--blackboxed-opacity);
}
.call-frame .icon {
display: inline-block;
vertical-align: top;
width: 16px;
height: 16px;
margin-inline-end: 3px;
}
.call-frame .titles {
display: inline-block;
}
.call-frame .subtitle,
.call-frame .subtitle .source-link {
color: hsla(0, 0%, 0%, 0.6);
text-decoration: none;
}
.call-frame:hover .subtitle .source-link,
.call-frame:focus .subtitle .source-link {
color: hsl(210, 0%, 0%);
}
.call-frame .subtitle:empty {
display: none;
}
.call-frame .subtitle {
font-size: inherit;
}
.call-frame .colon {
background-color: red;
}
.call-frame .separator {
white-space: nowrap;
color: hsla(0, 0%, 0%, 0.2);
}
@media (prefers-color-scheme: dark) {
.call-frame .subtitle,
.call-frame .subtitle .source-link {
color: hsla(0, 0%, var(--foreground-lightness), 0.6);
}
.call-frame:hover .subtitle .source-link,
.call-frame:focus .subtitle .source-link {
color: hsl(0, 0%, var(--foreground-lightness));
}
.call-frame .separator {
color: hsla(0, 0%, var(--foreground-lightness), 0.2);
}
}
+108
View File
@@ -0,0 +1,108 @@
/*
* 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.CallFrameView = class CallFrameView extends WI.Object
{
constructor(callFrame, {showFunctionName, indicateIfBlackboxed, isAsyncBoundaryCallFrame, isTruncatedBoundaryCallFrame} = {})
{
console.assert(callFrame instanceof WI.CallFrame);
var callFrameElement = document.createElement("div");
callFrameElement.classList.add("call-frame", WI.CallFrameView.iconClassNameForCallFrame(callFrame));
if (isAsyncBoundaryCallFrame)
callFrameElement.classList.add("async-boundary");
if (isTruncatedBoundaryCallFrame)
callFrameElement.classList.add("truncated-boundary");
var subtitleElement = document.createElement("span");
subtitleElement.classList.add("subtitle");
var sourceCodeLocation = callFrame.sourceCodeLocation;
if (sourceCodeLocation) {
if (indicateIfBlackboxed)
callFrameElement.classList.toggle("blackboxed", callFrame.blackboxed);
WI.linkifyElement(callFrameElement, sourceCodeLocation, {
ignoreNetworkTab: true,
ignoreSearchTab: true,
});
var linkElement = document.createElement("a");
linkElement.classList.add("source-link");
linkElement.href = sourceCodeLocation.sourceCode.url;
if (showFunctionName) {
var separatorElement = document.createElement("span");
separatorElement.classList.add("separator");
separatorElement.textContent = ` ${emDash} `;
subtitleElement.append(separatorElement);
}
subtitleElement.append(linkElement);
sourceCodeLocation.populateLiveDisplayLocationTooltip(linkElement);
sourceCodeLocation.populateLiveDisplayLocationString(linkElement, "textContent");
}
var titleElement = document.createElement("span");
titleElement.classList.add("title");
if (showFunctionName) {
var imgElement = document.createElement("img");
imgElement.classList.add("icon");
titleElement.append(imgElement, callFrame.displayName);
}
callFrameElement.append(titleElement, subtitleElement);
return callFrameElement;
}
static iconClassNameForCallFrame(callFrame)
{
if (callFrame.isTailDeleted)
return WI.CallFrameView.TailDeletedIcon;
if (callFrame.programCode)
return WI.CallFrameView.ProgramIconStyleClassName;
// This is more than likely an event listener function with an "on" prefix and it is
// as long or longer than the shortest event listener name -- "oncut".
if (callFrame.functionName && callFrame.functionName.startsWith("on") && callFrame.functionName.length >= 5)
return WI.CallFrameView.EventListenerIconStyleClassName;
if (callFrame.nativeCode)
return WI.CallFrameView.NativeIconStyleClassName;
return WI.CallFrameView.FunctionIconStyleClassName;
}
};
WI.CallFrameView.ProgramIconStyleClassName = "program-icon";
WI.CallFrameView.FunctionIconStyleClassName = "function-icon";
WI.CallFrameView.EventListenerIconStyleClassName = "event-listener-icon";
WI.CallFrameView.NativeIconStyleClassName = "native-icon";
WI.CallFrameView.TailDeletedIcon = "tail-deleted";
+62
View File
@@ -0,0 +1,62 @@
/*
* 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.
*/
.content-view.canvas {
background-color: hsl(0, 0%, 90%);
}
.content-view.canvas > .preview {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding: 15px;
}
.content-view.canvas {
display: flex;
flex-direction: column;
}
.content-view.canvas > .preview > img {
max-width: 100%;
max-height: 100%;
}
.content-view.canvas > :matches(header, footer) {
display: none;
}
.navigation-bar > .item.canvas-record.disabled {
filter: grayscale();
opacity: 0.5;
}
@media (prefers-color-scheme: dark) {
.content-view.canvas {
background-color: unset;
}
}
+442
View File
@@ -0,0 +1,442 @@
/*
* 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.CanvasContentView = class CanvasContentView extends WI.ContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.Canvas);
super(representedObject);
this.element.classList.add("canvas");
this._progressView = null;
this._previewContainerElement = null;
this._previewImageElement = null;
this._errorElement = null;
this._memoryCostElement = null;
this._pendingContent = null;
this._pixelSize = null;
this._pixelSizeElement = null;
this._canvasNode = null;
this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("refresh", WI.UIString("Refresh"), "Images/ReloadFull.svg", 13, 13);
this._refreshButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this.handleRefreshButtonClicked, this);
this._showGridButtonNavigationItem = new WI.ActivateButtonNavigationItem("show-grid", WI.repeatedUIString.showTransparencyGridTooltip(), WI.UIString("Hide transparency grid"), "Images/NavigationItemCheckers.svg", 13, 13);
this._showGridButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._showGridButtonClicked, this);
this._showGridButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._showGridButtonNavigationItem.activated = !!WI.settings.showImageGrid.value;
}
// Public
get navigationItems()
{
// The toggle recording NavigationItem isn't added to the ContentBrowser's NavigationBar.
// It's added to the "quick access" NavigationBar shown when hovering the canvas in the overview.
return [this._refreshButtonNavigationItem, this._showGridButtonNavigationItem];
}
refreshPreview()
{
this._pendingContent = null;
this.representedObject.requestContent().then((content) => {
this._pendingContent = content;
if (!this._pendingContent) {
this._showError();
return;
}
this.needsLayout();
});
}
handleRefreshButtonClicked()
{
this.refreshPreview();
}
// Protected
initialLayout()
{
super.initialLayout();
let isCard = !this._refreshButtonNavigationItem.parentNavigationBar;
if (isCard) {
let header = this.element.appendChild(document.createElement("header"));
header.addEventListener("click", (event) => { event.stopPropagation(); });
let titles = header.appendChild(document.createElement("div"));
titles.className = "titles";
let title = titles.appendChild(document.createElement("span"));
title.className = "title";
title.textContent = this.representedObject.displayName;
let subtitle = titles.appendChild(document.createElement("span"));
subtitle.className = "subtitle";
subtitle.textContent = WI.Canvas.displayNameForContextType(this.representedObject.contextType);
if (this.representedObject.contextAttributes.colorSpace) {
let subtitle = titles.appendChild(document.createElement("span"));
subtitle.className = "color-space";
subtitle.textContent = "(" + WI.Canvas.displayNameForColorSpace(this.representedObject.contextAttributes.colorSpace) + ")";
}
let navigationBar = new WI.NavigationBar;
if (this.representedObject.contextType === WI.Canvas.ContextType.Canvas2D || this.representedObject.contextType === WI.Canvas.ContextType.BitmapRenderer || this.representedObject.contextType === WI.Canvas.ContextType.WebGL || this.representedObject.contextType === WI.Canvas.ContextType.WebGL2) {
const toolTip = WI.UIString("Start recording canvas actions.\nShift-click to record a single frame.");
const altToolTip = WI.UIString("Stop recording canvas actions");
this._recordButtonNavigationItem = new WI.ToggleButtonNavigationItem("record-start-stop", toolTip, altToolTip, "Images/Record.svg", "Images/Stop.svg", 13, 13);
this._recordButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High;
this._recordButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleRecording, this);
navigationBar.addNavigationItem(this._recordButtonNavigationItem);
}
let canvasElementButtonNavigationItem = new WI.ButtonNavigationItem("canvas-element", WI.UIString("Canvas Element"), "Images/Markup.svg", 16, 16);
canvasElementButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
WI.addMouseDownContextMenuHandlers(canvasElementButtonNavigationItem.element, this._populateCanvasElementButtonContextMenu.bind(this));
navigationBar.addNavigationItem(canvasElementButtonNavigationItem);
navigationBar.addNavigationItem(this._refreshButtonNavigationItem);
header.append(navigationBar.element);
}
this._previewContainerElement = this.element.appendChild(document.createElement("div"));
this._previewContainerElement.className = "preview";
if (isCard) {
let footer = this.element.appendChild(document.createElement("footer"));
footer.addEventListener("click", (event) => { event.stopPropagation(); });
this._viewRelatedItemsContainer = footer.appendChild(document.createElement("div"));
this._viewRelatedItemsContainer.classList.add("view-related-items");
this._viewShaderButton = document.createElement("img");
this._viewShaderButton.classList.add("view-shader");
this._viewShaderButton.title = WI.UIString("View Shader");
WI.addMouseDownContextMenuHandlers(this._viewShaderButton, this._populateViewShaderButtonContextMenu.bind(this));
this._viewRecordingButton = document.createElement("img");
this._viewRecordingButton.classList.add("view-recording");
this._viewRecordingButton.title = WI.UIString("View Recording");
WI.addMouseDownContextMenuHandlers(this._viewRecordingButton, this._populateViewRecordingButtonContextMenu.bind(this));
this._updateViewRelatedItems();
let flexibleSpaceElement = footer.appendChild(document.createElement("div"));
flexibleSpaceElement.className = "flexible-space";
let metrics = footer.appendChild(document.createElement("div"));
this._pixelSizeElement = metrics.appendChild(document.createElement("span"));
this._pixelSizeElement.className = "pixel-size";
this._memoryCostElement = metrics.appendChild(document.createElement("span"));
this._memoryCostElement.className = "memory-cost";
}
if (this._errorElement)
this._showError();
if (isCard)
this._refreshPixelSize();
}
layout()
{
super.layout();
if (this._pendingContent) {
if (this._errorElement) {
this._errorElement.remove();
this._errorElement = null;
}
if (!this._previewImageElement) {
this._previewImageElement = document.createElement("img");
this._previewImageElement.addEventListener("error", this._showError.bind(this));
}
this._previewImageElement.src = this._pendingContent;
this._pendingContent = null;
if (!this._previewImageElement.parentNode)
this._previewContainerElement.appendChild(this._previewImageElement);
}
this._updateRecordNavigationItem();
this._updateProgressView();
this._updateViewRelatedItems();
this._updateMemoryCost();
this._updateImageGrid();
}
attached()
{
super.attached();
this.representedObject.addEventListener(WI.Canvas.Event.MemoryChanged, this._updateMemoryCost, this);
this.representedObject.addEventListener(WI.Canvas.Event.RecordingStarted, this.needsLayout, this);
this.representedObject.addEventListener(WI.Canvas.Event.RecordingProgress, this.needsLayout, this);
this.representedObject.addEventListener(WI.Canvas.Event.RecordingStopped, this.needsLayout, this);
this.representedObject.shaderProgramCollection.addEventListener(WI.Collection.Event.ItemAdded, this.needsLayout, this);
this.representedObject.shaderProgramCollection.addEventListener(WI.Collection.Event.ItemRemoved, this.needsLayout, this);
this.representedObject.requestNode().then((node) => {
if (!node)
return;
console.assert(!this._canvasNode || this._canvasNode === node);
if (this._canvasNode === node)
return;
this._canvasNode = node;
this._canvasNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._refreshPixelSize, this);
this._canvasNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._refreshPixelSize, this);
});
WI.settings.showImageGrid.addEventListener(WI.Setting.Event.Changed, this._updateImageGrid, this);
this.refreshPreview();
}
detached()
{
this.representedObject.removeEventListener(WI.Canvas.Event.MemoryChanged, this._updateMemoryCost, this);
this.representedObject.removeEventListener(WI.Canvas.Event.RecordingStarted, this.needsLayout, this);
this.representedObject.removeEventListener(WI.Canvas.Event.RecordingProgress, this.needsLayout, this);
this.representedObject.removeEventListener(WI.Canvas.Event.RecordingStopped, this.needsLayout, this);
this.representedObject.shaderProgramCollection.removeEventListener(WI.Collection.Event.ItemAdded, this.needsLayout, this);
this.representedObject.shaderProgramCollection.removeEventListener(WI.Collection.Event.ItemRemoved, this.needsLayout, this);
if (this._canvasNode) {
this._canvasNode.removeEventListener(WI.DOMNode.Event.AttributeModified, this._refreshPixelSize, this);
this._canvasNode.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._refreshPixelSize, this);
this._canvasNode = null;
}
WI.settings.showImageGrid.removeEventListener(WI.Setting.Event.Changed, this._updateImageGrid, this);
super.detached();
}
// Private
_showError()
{
if (this._previewImageElement)
this._previewImageElement.remove();
if (!this._errorElement) {
let isError = WI.Canvas.supportsRequestContentForContextType(this.representedObject.contextType);
this._errorElement = WI.createMessageTextView(WI.UIString("No Preview Available"), isError);
}
if (this._previewContainerElement)
this._previewContainerElement.appendChild(this._errorElement);
}
_toggleRecording(event)
{
if (this.representedObject.recordingActive)
this.representedObject.stopRecording();
else {
let singleFrame = event.data.nativeEvent.shiftKey;
this.representedObject.startRecording(singleFrame);
}
}
_refreshPixelSize()
{
let updatePixelSize = (size) => {
if (Object.shallowEqual(this._pixelSize, size))
return;
this._pixelSize = size;
if (this._pixelSizeElement) {
if (this._pixelSize)
this._pixelSizeElement.textContent = `${this._pixelSize.width} ${multiplicationSign} ${this._pixelSize.height}`;
else
this._pixelSizeElement.textContent = emDash;
}
this.refreshPreview();
};
this.representedObject.requestSize().then((size) => {
updatePixelSize(size);
});
}
_populateCanvasElementButtonContextMenu(contextMenu)
{
contextMenu.appendItem(WI.UIString("Log Canvas Context"), () => {
WI.RemoteObject.resolveCanvasContext(this.representedObject, WI.RuntimeManager.ConsoleObjectGroup, (remoteObject) => {
if (!remoteObject)
return;
const text = WI.UIString("Selected Canvas Context");
WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, {addSpecialUserLogClass: true});
});
});
contextMenu.appendSeparator();
if (this._canvasNode)
WI.appendContextMenuItemsForDOMNode(contextMenu, this._canvasNode);
}
_showGridButtonClicked()
{
WI.settings.showImageGrid.value = !this._showGridButtonNavigationItem.activated;
}
_updateImageGrid()
{
let activated = WI.settings.showImageGrid.value;
this._showGridButtonNavigationItem.activated = activated;
if (this._previewImageElement)
this._previewImageElement.classList.toggle("show-grid", activated);
}
_updateMemoryCost()
{
if (!this._memoryCostElement)
return;
let memoryCost = this.representedObject.memoryCost;
if (isNaN(memoryCost))
this._memoryCostElement.textContent = emDash;
else {
const higherResolution = false;
let bytesString = Number.bytesToString(memoryCost, higherResolution);
this._memoryCostElement.textContent = `(${bytesString})`;
}
}
_updateRecordNavigationItem()
{
if (!this._recordButtonNavigationItem)
return;
let recordingActive = this.representedObject.recordingActive;
this._recordButtonNavigationItem.toggled = recordingActive;
this._refreshButtonNavigationItem.enabled = !recordingActive;
this.element.classList.toggle("recording-active", recordingActive);
}
_updateProgressView()
{
if (!this._previewContainerElement)
return;
if (!this.representedObject.recordingActive) {
if (this._progressView && this._progressView.parentView) {
this.removeSubview(this._progressView);
this._progressView = null;
}
return;
}
if (!this._progressView) {
this._progressView = new WI.ProgressView;
this.element.insertBefore(this._progressView.element, this._previewContainerElement);
this.addSubview(this._progressView);
}
let title = null;
if (this.representedObject.recordingFrameCount) {
let formatString = this.representedObject.recordingFrameCount === 1 ? WI.UIString("%d Frame") : WI.UIString("%d Frames");
title = formatString.format(this.representedObject.recordingFrameCount);
} else
title = WI.UIString("Waiting for frames\u2026");
this._progressView.title = title;
this._progressView.subtitle = this.representedObject.recordingBufferUsed ? Number.bytesToString(this.representedObject.recordingBufferUsed) : "";
}
_updateViewRelatedItems()
{
if (!this._viewRelatedItemsContainer)
return;
this._viewRelatedItemsContainer.removeChildren();
if (this.representedObject.shaderProgramCollection.size)
this._viewRelatedItemsContainer.appendChild(this._viewShaderButton);
if (this.representedObject.recordingCollection.size)
this._viewRelatedItemsContainer.appendChild(this._viewRecordingButton);
}
_populateViewShaderButtonContextMenu(contextMenu, event)
{
let shaderPrograms = this.representedObject.shaderProgramCollection;
console.assert(shaderPrograms.size);
if (!shaderPrograms.size)
return;
if (event.button === 0 && shaderPrograms.size === 1) {
WI.showRepresentedObject(Array.from(shaderPrograms)[0]);
return;
}
for (let shaderProgram of shaderPrograms) {
contextMenu.appendItem(shaderProgram.displayName, () => {
WI.showRepresentedObject(shaderProgram);
});
}
}
_populateViewRecordingButtonContextMenu(contextMenu, event)
{
let recordings = this.representedObject.recordingCollection;
console.assert(recordings.size);
if (!recordings.size)
return;
if (event.button === 0 && recordings.size === 1) {
WI.showRepresentedObject(Array.from(recordings)[0]);
return;
}
for (let recording of recordings) {
contextMenu.appendItem(recording.displayName, () => {
WI.showRepresentedObject(recording);
});
}
}
};
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2017-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.
*/
.sidebar > .panel.details.canvas .details-section > .content .row.simple > .value > .node-link {
display: block;
}
.sidebar > .panel.details.canvas .details-section.canvas-extensions .content > ul {
margin: 4px 0;
padding-inline-start: 22.5px;
}
@@ -0,0 +1,355 @@
/*
* 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.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WI.DetailsSidebarPanel
{
constructor()
{
super("canvas", WI.UIString("Canvas"));
this.element.classList.add("canvas");
this._canvas = null;
this._node = null;
this._sections = [];
this._emptyContentPlaceholder = null;
}
// Public
inspect(objects)
{
if (!(objects instanceof Array))
objects = [objects];
objects = objects.map((object) => {
if (object instanceof WI.Recording)
return object.source;
if (object instanceof WI.ShaderProgram)
return object.canvas;
return object;
});
this.canvas = objects.find((object) => object instanceof WI.Canvas);
return !!this.canvas;
}
get canvas()
{
return this._canvas;
}
set canvas(canvas)
{
if (canvas === this._canvas)
return;
if (this._node) {
this._node.removeEventListener(WI.DOMNode.Event.AttributeModified, this._refreshSourceSection, this);
this._node.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._refreshSourceSection, this);
this._node = null;
}
if (this._canvas) {
this._canvas.removeEventListener(WI.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
this._canvas.removeEventListener(WI.Canvas.Event.ExtensionEnabled, this._refreshExtensionsSection, this);
this._canvas.removeEventListener(WI.Canvas.Event.ClientNodesChanged, this._refreshClientsSection, this);
}
this._canvas = canvas || null;
if (this._canvas) {
this._canvas.addEventListener(WI.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
this._canvas.addEventListener(WI.Canvas.Event.ExtensionEnabled, this._refreshExtensionsSection, this);
this._canvas.addEventListener(WI.Canvas.Event.ClientNodesChanged, this._refreshClientsSection, this);
}
this.needsLayout();
}
// Protected
initialLayout()
{
super.initialLayout();
this._nameRow = new WI.DetailsSectionSimpleRow(WI.UIString("Name"));
this._typeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Type"));
this._memoryRow = new WI.DetailsSectionSimpleRow(WI.UIString("Memory"));
this._memoryRow.tooltip = WI.UIString("Memory usage of this canvas");
let identitySection = new WI.DetailsSection("canvas-details", WI.UIString("Identity"));
identitySection.groups = [new WI.DetailsSectionGroup([this._nameRow, this._typeRow, this._memoryRow])];
this._sections.push(identitySection);
this._nodeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Node"));
this._cssCanvasRow = new WI.DetailsSectionSimpleRow(WI.UIString("CSS Canvas"));
this._widthRow = new WI.DetailsSectionSimpleRow(WI.UIString("Width"));
this._heightRow = new WI.DetailsSectionSimpleRow(WI.UIString("Height"));
this._detachedRow = new WI.DetailsSectionSimpleRow(WI.UIString("Detached"));
let sourceSection = new WI.DetailsSection("canvas-source", WI.UIString("Source"));
sourceSection.groups = [new WI.DetailsSectionGroup([this._nodeRow, this._cssCanvasRow, this._widthRow, this._heightRow, this._detachedRow])];
this._sections.push(sourceSection);
this._attributesDataGridRow = new WI.DetailsSectionDataGridRow(null, WI.UIString("No Attributes"));
this._attributesSection = new WI.DetailsSection("canvas-attributes", WI.UIString("Attributes"));
this._attributesSection.groups = [new WI.DetailsSectionGroup([this._attributesDataGridRow])];
this._attributesSection.element.hidden = true;
this._sections.push(this._attributesSection);
this._extensionsSection = new WI.DetailsSection("canvas-extensions", WI.UIString("Extensions"));
this._extensionsSection.element.hidden = true;
this._sections.push(this._extensionsSection);
this._clientNodesRow = new WI.DetailsSectionSimpleRow(WI.UIString("Nodes"));
this._clientsSection = new WI.DetailsSection("canvas-clients", WI.UIString("Clients"));
this._clientsSection.groups = [new WI.DetailsSectionGroup([this._clientNodesRow])];
this._clientsSection.element.hidden = true;
this._sections.push(this._clientsSection);
const selectable = false;
let backtraceTreeOutline = new WI.TreeOutline(selectable);
backtraceTreeOutline.disclosureButtons = false;
this._backtraceTreeController = new WI.StackTraceTreeController(backtraceTreeOutline);
let backtraceRow = new WI.DetailsSectionRow;
backtraceRow.element.appendChild(backtraceTreeOutline.element);
this._backtraceSection = new WI.DetailsSection("canvas-backtrace", WI.UIString("Backtrace"));
this._backtraceSection.groups = [new WI.DetailsSectionGroup([backtraceRow])];
this._backtraceSection.element.hidden = true;
this._sections.push(this._backtraceSection);
this._emptyContentPlaceholder = WI.createMessageTextView(WI.UIString("No Canvas Selected"));
}
layout()
{
super.layout();
this.contentView.element.removeChildren();
if (!this._canvas) {
this.contentView.element.appendChild(this._emptyContentPlaceholder);
return;
}
this.contentView.element.append(...this._sections.map(section => section.element));
this._refreshIdentitySection();
this._refreshSourceSection();
this._refreshAttributesSection();
this._refreshExtensionsSection();
this._refreshClientsSection();
this._refreshBacktraceSection();
}
sizeDidChange()
{
super.sizeDidChange();
// FIXME: <https://webkit.org/b/152269> Web Inspector: Convert DetailsSection classes to use View
this._attributesDataGridRow.sizeDidChange();
}
// Private
_refreshIdentitySection()
{
this._nameRow.value = this._canvas.displayName;
this._typeRow.value = WI.Canvas.displayNameForContextType(this._canvas.contextType);
this._formatMemoryRow();
}
_refreshSourceSection()
{
if (!this.didInitialLayout)
return;
let hideNode = this._canvas.cssCanvasName || this._canvas.contextType === WI.Canvas.ContextType.WebGPU;
this._nodeRow.value = hideNode ? null : emDash;
this._cssCanvasRow.value = this._canvas.cssCanvasName || null;
this._widthRow.value = emDash;
this._heightRow.value = emDash;
this._detachedRow.value = null;
this._canvas.requestNode().then((node) => {
if (!node) {
this._nodeRow.value = null;
return;
}
if (node !== this._node) {
if (this._node) {
this._node.removeEventListener(WI.DOMNode.Event.AttributeModified, this._refreshSourceSection, this);
this._node.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._refreshSourceSection, this);
this._node = null;
}
this._node = node;
this._node.addEventListener(WI.DOMNode.Event.AttributeModified, this._refreshSourceSection, this);
this._node.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._refreshSourceSection, this);
}
if (!hideNode) {
this._nodeRow.value = WI.linkifyNodeReference(this._node);
if (!this._node.parentNode)
this._detachedRow.value = WI.UIString("Yes");
}
let setRowValueIfValidAttributeValue = (row, attribute) => {
let value = Number(this._node.getAttribute(attribute));
if (!Number.isInteger(value) || value < 0)
return false;
row.value = value;
return true;
};
let validWidth = setRowValueIfValidAttributeValue(this._widthRow, "width");
let validHeight = setRowValueIfValidAttributeValue(this._heightRow, "height");
if (!validWidth || !validHeight) {
// Since the "width" and "height" properties of canvas elements are more than just
// attributes, we need to invoke the getter for each to get the actual value.
// - https://html.spec.whatwg.org/multipage/canvas.html#attr-canvas-width
// - https://html.spec.whatwg.org/multipage/canvas.html#attr-canvas-height
WI.RemoteObject.resolveNode(node).then((remoteObject) => {
function setRowValueToPropertyValue(row, property) {
remoteObject.getProperty(property, (error, result, wasThrown) => {
if (!error && result.type === "number")
row.value = `${result.value}px`;
});
}
setRowValueToPropertyValue(this._widthRow, "width");
setRowValueToPropertyValue(this._heightRow, "height");
remoteObject.release();
});
}
});
}
_refreshAttributesSection()
{
let hasAttributes = !isEmptyObject(this._canvas.contextAttributes);
this._attributesSection.element.hidden = !hasAttributes;
if (!hasAttributes)
return;
let dataGrid = this._attributesDataGridRow.dataGrid;
if (!dataGrid) {
dataGrid = this._attributesDataGridRow.dataGrid = new WI.DataGrid({
name: {title: WI.UIString("Name")},
value: {title: WI.UIString("Value"), width: "30%"},
});
}
dataGrid.removeChildren();
for (let attribute in this._canvas.contextAttributes) {
let data = {name: attribute, value: this._canvas.contextAttributes[attribute]};
let dataGridNode = new WI.DataGridNode(data);
dataGrid.appendChild(dataGridNode);
}
dataGrid.updateLayoutIfNeeded();
}
_refreshExtensionsSection()
{
let hasEnabledExtensions = this._canvas.extensions.size > 0;
this._extensionsSection.element.hidden = !hasEnabledExtensions;
if (!hasEnabledExtensions)
return;
let element = document.createElement("ul");
for (let extension of this._canvas.extensions) {
let listElement = element.appendChild(document.createElement("li"));
listElement.textContent = extension;
}
this._extensionsSection.groups = [{element}];
}
_refreshClientsSection()
{
if (!this.didInitialLayout)
return;
if (!this._canvas.cssCanvasName && this._canvas.contextType !== WI.Canvas.ContextType.WebGPU) {
this._clientsSection.element.hidden = true;
return;
}
this._clientNodesRow.value = emDash;
this._clientsSection.element.hidden = false;
this._canvas.requestClientNodes((clientNodes) => {
if (!clientNodes.length)
return;
let fragment = document.createDocumentFragment();
for (let clientNode of clientNodes)
fragment.appendChild(WI.linkifyNodeReference(clientNode));
this._clientNodesRow.value = fragment;
});
}
_refreshBacktraceSection()
{
let stackTrace = this._canvas.stackTrace;
this._backtraceTreeController.stackTrace = stackTrace;
this._backtraceSection.element.hidden = !stackTrace?.callFrames.length;
}
_formatMemoryRow()
{
if (!this.didInitialLayout)
return;
if (!this._canvas.memoryCost || isNaN(this._canvas.memoryCost)) {
this._memoryRow.value = emDash;
return;
}
this._memoryRow.value = Number.bytesToString(this._canvas.memoryCost);
}
_canvasMemoryChanged(event)
{
this._formatMemoryRow();
}
};
@@ -0,0 +1,253 @@
/*
* Copyright (C) 2017-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.
*/
.content-view.canvas-overview {
justify-content: center;
align-items: flex-start;
position: relative;
padding: 4px 0;
}
.content-view.canvas-overview > .content-view.canvas {
flex-grow: 0;
flex-shrink: 0;
width: 400px;
margin: 4px;
border: 1px solid var(--border-color);
cursor: pointer;
}
.content-view.canvas-overview > .content-view.canvas,
.content-view.canvas-overview > .content-view.canvas > .preview > img {
background-color: white;
}
.content-view.canvas-overview > .content-view.canvas.recording-active {
border-color: red;
}
.content-view.canvas-overview > .content-view.canvas > :matches(header, footer) {
display: flex;
flex-direction: row;
flex-shrink: 0;
justify-content: space-between;
align-items: center;
padding: 0 6px;
height: var(--navigation-bar-height);
cursor: default;
}
.content-view.canvas-overview > .content-view.canvas > header {
font-size: 13px;
}
.content-view.canvas-overview > .content-view.canvas.recording-active > header {
background-color: red;
}
.content-view.canvas-overview > .content-view.canvas > header > .titles,
.content-view.canvas-overview > .content-view.canvas > footer > .size {
white-space: nowrap;
}
.content-view.canvas-overview > .content-view.canvas > header > .titles > .title {
color: var(--text-color-gray-dark);
}
.content-view.canvas-overview > .content-view.canvas > header > .titles > :matches(.subtitle, .color-space),
.content-view.canvas-overview > .content-view.canvas > footer .memory-cost {
color: var(--text-color-gray-medium);
}
.content-view.canvas-overview > .content-view.canvas > header .subtitle::before {
content: "\00A0\2014\00A0"; /* &nbsp;&mdash;&nbsp; */;
}
.content-view.canvas-overview > .content-view.canvas > header .color-space::before {
content: "\00A0"; /* &nbsp; */;
}
.content-view.canvas-overview > .content-view.canvas.recording-active > header > .titles > .title {
color: white;
}
.content-view.canvas-overview > .content-view.canvas.recording-active > header > .titles > .subtitle {
color: var(--selected-secondary-text-color);
}
.content-view.canvas-overview > .content-view.canvas.recording-active > header > .navigation-bar > .item {
filter: brightness(0) invert();
}
.content-view.canvas-overview > .content-view.canvas > header > .navigation-bar {
align-items: initial;
border: none;
opacity: 0;
transition: opacity 200ms ease-in-out;
}
.content-view.canvas-overview > .content-view.canvas:matches(:hover, .recording-active) > header > .navigation-bar {
opacity: 1;
transition: opacity 200ms ease-in-out;
}
.content-view.canvas-overview > .content-view.canvas:not(.recording-active) > header > .navigation-bar > .item.record-start-stop.disabled {
filter: grayscale();
opacity: 0.5;
}
.content-view.canvas-overview > .content-view.canvas:not(.recording-active) > header > .navigation-bar > .item.record-start-stop:not(.disabled):hover {
filter: brightness(95%);
}
.content-view.canvas-overview > .content-view.canvas:not(.recording-active) > header > .navigation-bar > .item.record-start-stop:not(.disabled):active {
filter: brightness(80%);
}
.content-view.canvas-overview > .content-view.canvas.recording-active > .progress-view,
.content-view.canvas-overview > .content-view.canvas > .preview {
height: 280px;
transition: background-color 200ms ease-in-out;
}
.content-view.canvas-overview > .content-view.canvas.recording-active > .progress-view:hover,
.content-view.canvas-overview > .content-view.canvas > .preview:hover {
background-color: hsl(0, 0%, 96%);
}
.content-view.canvas-overview > .content-view.canvas.recording-active > .preview {
display: none;
}
.content-view.canvas-overview > .content-view.canvas > .preview > img {
border-radius: 4px;
box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.58);
}
.content-view.canvas-overview > .content-view.canvas > .preview > .message-text-view {
position: static;
z-index: 0;
}
.content-view.canvas-overview > .content-view.canvas > footer {
border-top: none;
}
.content-view.canvas-overview > .content-view.canvas > footer > .view-related-items {
display: flex;
align-items: center;
}
.content-view.canvas-overview > .content-view.canvas > footer > .view-related-items > :matches(.view-shader, .view-recording) {
width: 16px;
height: 16px;
}
.content-view.canvas-overview > .content-view.canvas > footer > .view-related-items > img + img {
margin-inline-start: 4px;
}
.content-view.canvas-overview > .content-view.canvas > footer > .view-related-items > .view-shader {
content: url(../Images/DocumentIcons.svg#gl-light);
}
.content-view.canvas-overview > .content-view.canvas > footer > .view-related-items > .view-recording {
content: url(../Images/Recording.svg);
}
.content-view.canvas-overview > .content-view.canvas > footer > .flexible-space {
flex: 1;
}
.content-view.canvas-overview > .content-view.canvas > footer .memory-cost {
padding-inline-start: 4px;
}
.content-view.canvas-overview > .content-view.canvas.saved-recordings {
height: 340px;
}
.content-view.canvas-overview > .content-view.canvas.saved-recordings .tree-outline {
overflow-y: auto;
}
.content-view.canvas-overview > .content-view.canvas.saved-recordings .tree-outline > .item.recording > .icon {
content: url(../Images/Recording.svg);
}
.navigation-bar > .item.canvas-recording-auto-capture > label {
display: flex;
align-items: center;
}
.navigation-bar > .item.canvas-recording-auto-capture > label > input {
width: 1.5em;
min-width: 1.5em;
margin: 0 4px;
text-align: center;
}
@media (prefers-color-scheme: dark) {
.content-view.canvas-overview > .content-view.canvas,
.content-view.canvas-overview > .content-view.canvas > .preview > img {
background-color: var(--background-color-secondary);
}
.content-view.canvas-overview > .content-view.canvas.recording-active > .progress-view:hover,
.content-view.canvas-overview > .content-view.canvas > .preview:hover {
background-color: var(--background-color-tertiary);
}
.content-view.canvas-overview > .content-view.canvas.recording-active {
--recording-color: hsl(0, 100%, 39%);
border-color: var(--recording-color);
}
.content-view.canvas-overview > .content-view.canvas.recording-active > header {
background-color: var(--recording-color);
}
.content-view.canvas-overview > .content-view.canvas > header > .titles > .title {
color: var(--text-color);
}
.content-view.canvas-overview > .content-view.canvas > header > .titles > .subtitle,
.content-view.canvas-overview > .content-view.canvas > footer .memory-cost {
color: var(--text-color-secondary);
}
.content-view.canvas-overview > .content-view.canvas > footer .view-recording {
filter: invert();
}
.content-view.canvas-overview > .content-view.canvas.recording-active > header > .titles > .subtitle {
color: unset;
opacity: 0.5
}
.content-view.canvas-overview > .content-view.canvas > footer > .view-related-items > .view-shader {
content: url(../Images/DocumentIcons.svg#gl-dark);
}
}
@@ -0,0 +1,297 @@
/*
* 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.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.CollectionContentView
{
constructor(representedObject)
{
console.assert(representedObject instanceof WI.CanvasCollection);
let contentPlaceholder = WI.animationManager.supported ? document.createElement("div") : WI.createMessageTextView(WI.UIString("No Canvas Contexts"));
let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
descriptionElement.className = "description";
descriptionElement.textContent = WI.UIString("Waiting for canvas contexts created by script or CSS.");
let importNavigationItem = new WI.ButtonNavigationItem("import-recording", WI.UIString("Import"), "Images/Import.svg", 15, 15);
importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to load a recording from file."), importNavigationItem);
contentPlaceholder.appendChild(importHelpElement);
super(representedObject, WI.CanvasContentView, contentPlaceholder);
this.element.classList.add("canvas-overview");
if (WI.CanvasManager.supportsRecordingAutoCapture()) {
this._recordingAutoCaptureFrameCountInputElement = document.createElement("input");
this._recordingAutoCaptureFrameCountInputElement.type = "number";
this._recordingAutoCaptureFrameCountInputElement.min = 0;
this._recordingAutoCaptureFrameCountInputElement.addEventListener("input", this._handleRecordingAutoCaptureInput.bind(this));
this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value;
const label = null;
this._recordingAutoCaptureNavigationItem = new WI.CheckboxNavigationItem("canvas-recording-auto-capture", label, !!WI.settings.canvasRecordingAutoCaptureEnabled.value);
this._recordingAutoCaptureNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._recordingAutoCaptureNavigationItem.addEventListener(WI.CheckboxNavigationItem.Event.CheckedDidChange, this._handleRecordingAutoCaptureCheckedDidChange, this);
let frameCount = this._updateRecordingAutoCaptureInputElementSize();
this._setRecordingAutoCaptureFrameCount(frameCount);
this._updateRecordingAutoCaptureCheckboxLabel(frameCount);
}
importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
this._savedRecordingsContentView = null;
this._savedRecordingsTreeOutline = null;
}
// Public
get navigationItems()
{
let navigationItems = [];
if (this._recordingAutoCaptureNavigationItem)
navigationItems.push(this._recordingAutoCaptureNavigationItem);
return navigationItems;
}
handleRefreshButtonClicked()
{
for (let subview of this.subviews) {
if (subview instanceof WI.CanvasContentView)
subview.handleRefreshButtonClicked();
}
}
// Protected
contentViewAdded(contentView)
{
contentView.element.addEventListener("mouseenter", this._contentViewMouseEnter);
contentView.element.addEventListener("mouseleave", this._contentViewMouseLeave);
if (this._savedRecordingsContentView) {
// Ensure that the imported recordings are always last.
this.removeSubview(this._savedRecordingsContentView);
this.addSubview(this._savedRecordingsContentView);
}
}
contentViewRemoved(contentView)
{
contentView.element.removeEventListener("mouseenter", this._contentViewMouseEnter);
contentView.element.removeEventListener("mouseleave", this._contentViewMouseLeave);
}
attached()
{
super.attached();
WI.settings.canvasRecordingAutoCaptureEnabled.addEventListener(WI.Setting.Event.Changed, this._handleCanvasRecordingAutoCaptureEnabledChanged, this);
WI.settings.canvasRecordingAutoCaptureFrameCount.addEventListener(WI.Setting.Event.Changed, this._handleCanvasRecordingAutoCaptureFrameCountChanged, this);
WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingSaved, this._handleRecordingSaved, this);
if (this._savedRecordingsTreeOutline)
this._savedRecordingsTreeOutline.removeChildren();
for (let recording of WI.canvasManager.savedRecordings)
this._addSavedRecording(recording);
for (let subview of this.subviews) {
if (subview instanceof WI.CanvasContentView)
subview.refreshPreview();
}
}
detached()
{
WI.domManager.hideDOMNodeHighlight();
WI.canvasManager.removeEventListener(WI.CanvasManager.Event.RecordingSaved, this._handleRecordingSaved, this);
WI.settings.canvasRecordingAutoCaptureFrameCount.removeEventListener(WI.Setting.Event.Changed, this._handleCanvasRecordingAutoCaptureFrameCountChanged, this);
WI.settings.canvasRecordingAutoCaptureEnabled.removeEventListener(WI.Setting.Event.Changed, this._handleCanvasRecordingAutoCaptureEnabledChanged, this);
super.detached();
}
// Private
_contentViewMouseEnter(event)
{
let contentView = WI.View.fromElement(event.target);
if (!(contentView instanceof WI.CanvasContentView))
return;
let canvas = contentView.representedObject;
if (canvas.cssCanvasName || canvas.contextType === WI.Canvas.ContextType.WebGPU) {
canvas.requestClientNodes((clientNodes) => {
WI.domManager.highlightDOMNodeList(clientNodes);
});
return;
}
canvas.requestNode().then((node) => {
if (!node || !node.ownerDocument)
return;
node.highlight();
});
}
_contentViewMouseLeave(event)
{
WI.domManager.hideDOMNodeHighlight();
}
_setRecordingAutoCaptureFrameCount(frameCount)
{
console.assert(!isNaN(frameCount) && frameCount >= 0);
if (this._recordingAutoCaptureNavigationItem.checked)
frameCount = Math.max(1, frameCount);
let enabled = frameCount > 0 && !!this._recordingAutoCaptureNavigationItem.checked;
WI.canvasManager.setRecordingAutoCaptureFrameCount(enabled, frameCount);
}
_updateRecordingAutoCaptureCheckboxLabel(frameCount)
{
let active = document.activeElement === this._recordingAutoCaptureFrameCountInputElement;
let selectionStart = this._recordingAutoCaptureFrameCountInputElement.selectionStart;
let selectionEnd = this._recordingAutoCaptureFrameCountInputElement.selectionEnd;
let direction = this._recordingAutoCaptureFrameCountInputElement.direction;
let label = frameCount === 1 ? WI.UIString("Record first %s frame") : WI.UIString("Record first %s frames");
let fragment = document.createDocumentFragment();
String.format(label, [this._recordingAutoCaptureFrameCountInputElement], String.standardFormatters, fragment, (a, b) => {
a.append(b);
return a;
});
this._recordingAutoCaptureNavigationItem.label = fragment;
if (active) {
this._recordingAutoCaptureFrameCountInputElement.selectionStart = selectionStart;
this._recordingAutoCaptureFrameCountInputElement.selectionEnd = selectionEnd;
this._recordingAutoCaptureFrameCountInputElement.direction = direction;
}
}
get _recordingAutoCaptureFrameCountInputElementValue()
{
return parseInt(this._recordingAutoCaptureFrameCountInputElement.value);
}
set _recordingAutoCaptureFrameCountInputElementValue(frameCount)
{
if (this._recordingAutoCaptureFrameCountInputElement.value || frameCount)
this._recordingAutoCaptureFrameCountInputElement.value = frameCount;
this._recordingAutoCaptureFrameCountInputElement.placeholder = frameCount;
}
_updateRecordingAutoCaptureInputElementSize()
{
let frameCount = this._recordingAutoCaptureFrameCountInputElementValue;
if (isNaN(frameCount) || frameCount < 0) {
frameCount = 0;
this._recordingAutoCaptureFrameCountInputElementValue = frameCount;
}
this._recordingAutoCaptureFrameCountInputElement.autosize();
return frameCount;
}
_addSavedRecording(recording)
{
console.assert(!recording.source);
if (!this._savedRecordingsContentView) {
this._savedRecordingsContentView = new WI.ContentView;
this._savedRecordingsContentView.element.classList.add("canvas", "saved-recordings");
this.addSubview(this._savedRecordingsContentView);
let header = this._savedRecordingsContentView.element.appendChild(document.createElement("header"));
header.textContent = WI.UIString("Saved Recordings");
this.hideContentPlaceholder();
}
if (!this._savedRecordingsTreeOutline) {
const selectable = false;
this._savedRecordingsTreeOutline = new WI.TreeOutline(selectable);
this._savedRecordingsTreeOutline.addEventListener(WI.TreeOutline.Event.ElementClicked, this._handleSavedRecordingClicked, this);
this._savedRecordingsContentView.element.appendChild(this._savedRecordingsTreeOutline.element);
}
let recordingTreeElement = new WI.GeneralTreeElement(["recording"], recording.displayName, WI.Recording.displayNameForRecordingType(recording.type), recording);
recordingTreeElement.selectable = false;
this._savedRecordingsTreeOutline.appendChild(recordingTreeElement);
}
_handleRecordingAutoCaptureInput(event)
{
let frameCount = this._updateRecordingAutoCaptureInputElementSize();
this._recordingAutoCaptureNavigationItem.checked = !!frameCount;
this._setRecordingAutoCaptureFrameCount(frameCount);
}
_handleRecordingAutoCaptureCheckedDidChange(event)
{
this._setRecordingAutoCaptureFrameCount(this._recordingAutoCaptureFrameCountInputElementValue || 0);
}
_handleCanvasRecordingAutoCaptureEnabledChanged(event)
{
this._recordingAutoCaptureNavigationItem.checked = WI.settings.canvasRecordingAutoCaptureEnabled.value;
}
_handleCanvasRecordingAutoCaptureFrameCountChanged(event)
{
// Only update the value if it is different to prevent mangling the selection.
if (this._recordingAutoCaptureFrameCountInputElementValue !== WI.settings.canvasRecordingAutoCaptureFrameCount.value)
this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value;
this._updateRecordingAutoCaptureCheckboxLabel(WI.settings.canvasRecordingAutoCaptureFrameCount.value);
}
_handleImportButtonNavigationItemClicked(event)
{
WI.FileUtilities.importJSON((result) => WI.canvasManager.processJSON(result), {multiple: true});
}
_handleRecordingSaved(event)
{
this._addSavedRecording(event.data.recording);
}
_handleSavedRecordingClicked(event)
{
WI.showRepresentedObject(event.data.treeElement.representedObject);
}
};
+125
View File
@@ -0,0 +1,125 @@
/*
* Copyright (C) 2018-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.
*/
.sidebar > .panel.navigation.canvas > .content {
display: flex;
flex-direction: column;
top: var(--navigation-bar-height);
overflow-y: hidden;
}
.sidebar > .panel.navigation.canvas > .navigation-bar > .item.record-start-stop.disabled > .glyph {
filter: grayscale();
opacity: 0.5;
}
.sidebar > .panel.navigation.canvas > .content > .tree-outline .item.canvas:matches(.canvas-2d, .bitmaprenderer) .icon {
content: url(../Images/Canvas2D.svg);
}
.sidebar > .panel.navigation.canvas > .content > .tree-outline .item.canvas:matches(.webgl, .webgl2, .webgpu, .webmetal) .icon {
content: url(../Images/Canvas3D.svg);
}
.sidebar > .panel.navigation.canvas > .content > .navigation-bar {
border-top: 1px solid var(--border-color);
}
.sidebar > .panel.navigation.canvas.has-recordings > .content > .recording-content {
flex-grow: 1;
overflow-y: auto;
}
.sidebar > .panel.navigation.canvas.showing-recording > .content > .tree-outline.canvas {
flex-shrink: 0;
height: fit-content;
max-height: 110px;
overflow-y: auto;
}
.sidebar > .panel.navigation.canvas:not(.has-recordings) > .filter-bar,
.sidebar > .panel.navigation.canvas:not(.has-recordings) > .content > :matches(.navigation-bar, .recording-content) {
display: none;
}
.sidebar > .panel.navigation.canvas > .content > .tree-outline .item.shader-program > .icon {
content: url(../Images/DocumentIcons.svg#gl-light);
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.recording > .icon {
content: url(../Images/Recording.svg);
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.folder-icon > .icon {
content: url(../Images/TypeIcons.svg#RenderingFrame-light);
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.folder-icon > .status {
line-height: 16px;
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle > progress {
width: 100%;
max-width: 100px;
margin: 0 4px;
vertical-align: -3px;
}
body:not(.window-inactive, .window-docked-inactive) .sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline:focus-within .item.processing.selected .subtitle > progress {
filter: brightness(10);
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle::before {
content: "";
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options {
display: flex;
flex-direction: column;
align-items: center;
margin: 16px 0;
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options > .indeterminate-progress-spinner {
margin-bottom: 4px;
}
@media (prefers-color-scheme: dark) {
.sidebar > .panel.navigation.canvas > .navigation-bar > .item.record-start-stop.disabled > .glyph {
filter: grayscale() invert();
}
.sidebar > .panel.navigation.canvas > .content > .tree-outline .item.canvas .icon {
filter: invert();
}
.sidebar > .panel.navigation.canvas > .content > .tree-outline .item.shader-program > .icon {
content: url(../Images/DocumentIcons.svg#gl-dark);
}
.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.folder-icon > .icon {
content: url(../Images/TypeIcons.svg#RenderingFrame-dark);
}
}
+625
View File
@@ -0,0 +1,625 @@
/*
* 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.CanvasSidebarPanel = class CanvasSidebarPanel extends WI.NavigationSidebarPanel
{
constructor()
{
super("canvas", WI.UIString("Canvas"));
this._canvas = null;
this._recording = null;
this._navigationBar = new WI.NavigationBar;
this._scopeBar = null;
this._placeholderScopeBarItem = null;
const toolTip = WI.UIString("Start recording canvas actions.\nShift-click to record a single frame.");
const altToolTip = WI.UIString("Stop recording canvas actions");
this._recordButtonNavigationItem = new WI.ToggleButtonNavigationItem("record-start-stop", toolTip, altToolTip, "Images/Record.svg", "Images/Stop.svg", 13, 13);
this._recordButtonNavigationItem.enabled = false;
this._recordButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._recordButtonNavigationItem.label = WI.UIString("Start");
this._recordButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleRecording, this);
this._navigationBar.addNavigationItem(this._recordButtonNavigationItem);
this._navigationBar.addNavigationItem(new WI.DividerNavigationItem);
let importButtonNavigationItem = new WI.ButtonNavigationItem("import-recording", WI.UIString("Import"), "Images/Import.svg", 15, 15);
importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
this._navigationBar.addNavigationItem(importButtonNavigationItem);
this.addSubview(this._navigationBar);
this._canvasTreeOutline = this.createContentTreeOutline({suppressFiltering: true});
this._canvasTreeOutline.element.classList.add("canvas");
this._recordingNavigationBar = new WI.NavigationBar;
this._recordingNavigationBar.element.classList.add("hidden");
this.contentView.addSubview(this._recordingNavigationBar);
this._recordingContentContainer = this.contentView.element.appendChild(document.createElement("div"));
this._recordingContentContainer.className = "recording-content";
this._recordingTreeOutline = this.contentTreeOutline;
this._recordingContentContainer.appendChild(this._recordingTreeOutline.element);
this._recordingTreeOutline.customIndent = true;
this._recordingTreeOutline.registerScrollVirtualizer(this._recordingContentContainer, 20);
this._canvasTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
this._recordingTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
this._recordingProcessingOptionsContainer = null;
this._selectedRecordingActionIndex = NaN;
}
// Public
get canvas()
{
return this._canvas;
}
set canvas(canvas)
{
if (this._canvas === canvas)
return;
if (this._canvas) {
this._canvas.removeEventListener(WI.Canvas.Event.RecordingStarted, this._updateRecordNavigationItem, this);
this._canvas.removeEventListener(WI.Canvas.Event.RecordingStopped, this._updateRecordNavigationItem, this);
this._canvas.recordingCollection.removeEventListener(WI.Collection.Event.ItemAdded, this._recordingAdded, this);
this._canvas.recordingCollection.removeEventListener(WI.Collection.Event.ItemRemoved, this._recordingRemoved, this);
}
this._canvas = canvas;
if (this._canvas) {
this._canvas.addEventListener(WI.Canvas.Event.RecordingStarted, this._updateRecordNavigationItem, this);
this._canvas.addEventListener(WI.Canvas.Event.RecordingStopped, this._updateRecordNavigationItem, this);
this._canvas.recordingCollection.addEventListener(WI.Collection.Event.ItemAdded, this._recordingAdded, this);
this._canvas.recordingCollection.addEventListener(WI.Collection.Event.ItemRemoved, this._recordingRemoved, this);
}
this._canvasChanged();
this._updateRecordNavigationItem();
this._updateRecordingScopeBar();
}
set recording(recording)
{
if (recording === this._recording)
return;
if (this._recording && !this._recording.ready) {
this._recording.removeEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
this._recording.removeEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
}
if (recording)
this.canvas = recording.source;
this._recording = recording;
if (this._recording && !this._recording.ready) {
this._recording.addEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
this._recording.addEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
}
this._updateRecordNavigationItem();
this._updateRecordingScopeBar();
this._recordingChanged();
}
set action(action)
{
if (!this._recording)
return;
if (action === this._recording.actions[this._selectedRecordingActionIndex])
return;
let selectedTreeElement = this._recordingTreeOutline.selectedTreeElement;
if (!action) {
if (selectedTreeElement)
selectedTreeElement.deselect();
return;
}
if (selectedTreeElement && selectedTreeElement instanceof WI.FolderTreeElement) {
let lastActionTreeElement = selectedTreeElement.children.lastValue;
if (action === lastActionTreeElement.representedObject)
return;
}
let treeElement = this._recordingTreeOutline.findTreeElement(action);
console.assert(treeElement, "Missing tree element for recording action.", action);
if (!treeElement)
return;
this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] = action;
const omitFocus = false;
const selectedByUser = false;
treeElement.revealAndSelect(omitFocus, selectedByUser);
this._selectedRecordingActionIndex = this._recording.actions.indexOf(action);
}
updateRepresentedObjects()
{
let objects = this.contentBrowser.currentRepresentedObjects;
let canvas = objects.find((object) => object instanceof WI.Canvas);
if (canvas) {
this.canvas = canvas;
let treeElement = this._canvasTreeOutline.findTreeElement(canvas);
const omitFocus = false;
const selectedByUser = false;
treeElement.revealAndSelect(omitFocus, selectedByUser);
return;
}
let shaderProgram = objects.find((object) => object instanceof WI.ShaderProgram);
if (shaderProgram) {
this.canvas = shaderProgram.canvas;
let treeElement = this._canvasTreeOutline.findTreeElement(shaderProgram);
const omitFocus = false;
const selectedByUser = false;
treeElement.revealAndSelect(omitFocus, selectedByUser);
return;
}
let recording = objects.find((object) => object instanceof WI.Recording);
if (recording) {
this.canvas = recording.source;
this.recording = recording;
let recordingAction = objects.find((object) => object instanceof WI.RecordingAction);
if (recordingAction !== recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
this.action = recordingAction;
return;
}
this.canvas = null;
this.recording = null;
}
attached()
{
super.attached();
this.contentBrowser.addEventListener(WI.ContentBrowser.Event.CurrentRepresentedObjectsDidChange, this.updateRepresentedObjects, this);
this.updateRepresentedObjects();
if (this._recording) {
this._recordingTreeOutline.updateVirtualizedElementsDebouncer.force();
let action = this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol];
let treeElement = this._recordingTreeOutline.findTreeElement(action);
if (treeElement) {
const omitFocus = false;
const selectedByUser = false;
treeElement.revealAndSelect(omitFocus, selectedByUser);
}
}
}
detached()
{
this.contentBrowser.removeEventListener(WI.ContentBrowser.Event.CurrentRepresentedObjectsDidChange, this.updateRepresentedObjects, this);
super.detached();
}
canShowRepresentedObject(representedObject)
{
return representedObject instanceof WI.Canvas
|| representedObject instanceof WI.ShaderProgram
|| representedObject instanceof WI.Recording;
}
// Protected
get scrollElement()
{
return this._recordingContentContainer;
}
hasCustomFilters()
{
return true;
}
matchTreeElementAgainstCustomFilters(treeElement)
{
// Keep recording frame tree elements.
if (treeElement instanceof WI.FolderTreeElement)
return true;
// Always show the Initial State tree element.
if (treeElement instanceof WI.RecordingActionTreeElement && treeElement.representedObject instanceof WI.RecordingInitialStateAction)
return true;
return super.matchTreeElementAgainstCustomFilters(treeElement);
}
initialLayout()
{
super.initialLayout();
let filterFunction = (treeElement) => {
if (!(treeElement.representedObject instanceof WI.RecordingAction))
return false;
return treeElement.representedObject.isVisual || treeElement.representedObject instanceof WI.RecordingInitialStateAction;
};
const activatedByDefault = false;
const defaultToolTip = WI.UIString("Only show visual actions");
const activatedToolTip = WI.UIString("Show all actions");
this.filterBar.addFilterBarButton("recording-show-visual-only", filterFunction, activatedByDefault, defaultToolTip, activatedToolTip, "Images/Paint.svg", 15, 15);
}
// Private
_recordingAdded(event)
{
this.recording = event.data.item;
}
_recordingRemoved(event)
{
this._updateRecordingScopeBar();
let recording = event.data.item;
if (recording === this.recording)
this.recording = this._canvas ? Array.from(this._canvas.recordingCollection).lastValue : null;
}
_scopeBarSelectionChanged()
{
let selectedScopeBarItem = this._scopeBar.selectedItems[0];
this.recording = selectedScopeBarItem.__recording || null;
}
_toggleRecording(event)
{
if (!this._canvas)
return;
if (this._canvas.recordingActive)
this._canvas.stopRecording();
else {
let singleFrame = event.data.nativeEvent.shiftKey;
this._canvas.startRecording(singleFrame);
}
}
_handleImportButtonNavigationItemClicked(event)
{
WI.FileUtilities.importJSON((result) => WI.canvasManager.processJSON(result), {multiple: true});
}
_treeSelectionDidChange(event)
{
let treeElement = event.target.selectedTreeElement;
if (!treeElement)
return;
if ((treeElement instanceof WI.CanvasTreeElement) || (treeElement instanceof WI.ShaderProgramTreeElement)) {
if (this._placeholderScopeBarItem)
this._placeholderScopeBarItem.selected = true;
this.showDefaultContentViewForTreeElement(treeElement);
return;
}
if (treeElement instanceof WI.FolderTreeElement)
treeElement = treeElement.children.lastValue;
if (!(treeElement instanceof WI.RecordingActionTreeElement))
return;
console.assert(this._recording, "Missing recording for action tree element.", treeElement);
this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] = treeElement.representedObject;
const onlyExisting = true;
let recordingContentView = this.contentBrowser.contentViewForRepresentedObject(this._recording, onlyExisting);
if (!recordingContentView)
return;
this.contentBrowser.showContentView(recordingContentView);
this._selectedRecordingActionIndex = treeElement.index;
recordingContentView.updateActionIndex(this._selectedRecordingActionIndex);
}
_canvasChanged()
{
this._canvasTreeOutline.removeChildren();
if (!this._canvas) {
this._recordingNavigationBar.element.classList.add("hidden");
return;
}
const showRecordings = false;
let canvasTreeElement = new WI.CanvasTreeElement(this._canvas, showRecordings);
canvasTreeElement.expanded = true;
this._canvasTreeOutline.appendChild(canvasTreeElement);
const omitFocus = false;
const selectedByUser = false;
canvasTreeElement.revealAndSelect(omitFocus, selectedByUser);
if (WI.Canvas.ContextType.Canvas2D || this._canvas.contextType === WI.Canvas.ContextType.WebGL || this._canvas.contextType === WI.Canvas.ContextType.WebGL2)
this._recordButtonNavigationItem.enabled = true;
this.recording = null;
}
_recordingChanged()
{
this._recordingTreeOutline.removeChildren();
this._selectedRecordingActionIndex = NaN;
if (this._recordingProcessingOptionsContainer) {
this._recordingProcessingOptionsContainer.remove();
this._recordingProcessingOptionsContainer = null;
}
if (!this._recording)
return;
if (!this._recording.ready) {
if (!this._recording.processing)
this._recording.startProcessing();
if (!this._recordingProcessingOptionsContainer) {
this._recordingProcessingOptionsContainer = this._recordingContentContainer.appendChild(document.createElement("div"));
this._recordingProcessingOptionsContainer.classList.add("recording-processing-options");
let createPauseButton = () => {
let spinner = new WI.IndeterminateProgressSpinner;
this._recordingProcessingOptionsContainer.appendChild(spinner.element);
let pauseButton = this._recordingProcessingOptionsContainer.appendChild(document.createElement("button"));
pauseButton.textContent = WI.UIString("Pause Processing");
pauseButton.addEventListener("click", (event) => {
this._recording.stopProcessing();
spinner.element.remove();
pauseButton.remove();
createResumeButton();
});
};
let createResumeButton = () => {
let resumeButton = this._recordingProcessingOptionsContainer.appendChild(document.createElement("button"));
resumeButton.textContent = WI.UIString("Resume Processing");
resumeButton.addEventListener("click", (event) => {
this._recording.startProcessing();
resumeButton.remove();
createPauseButton();
});
};
if (this._recording.processing)
createPauseButton();
else
createResumeButton();
}
}
this.contentBrowser.showContentViewForRepresentedObject(this._recording);
if (this._scopeBar) {
let scopeBarItem = this._scopeBar.item(this._recording.displayName);
console.assert(scopeBarItem, "Missing scopeBarItem for recording.", this._recording);
scopeBarItem.selected = true;
}
let initialStateAction = this._recording.actions[0];
if (initialStateAction.ready && !this._recordingTreeOutline.getCachedTreeElement(initialStateAction)) {
this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(initialStateAction, 0, this._recording.type));
if (!this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
this.action = initialStateAction;
}
let cumulativeActionIndex = 0;
this._recording.frames.forEach((frame, frameIndex) => {
if (!frame.actions[0].ready)
return;
let folder = this._recordingTreeOutline.getCachedTreeElement(frame);
if (!folder)
folder = this._createRecordingFrameTreeElement(frame, frameIndex, this._recordingTreeOutline);
for (let action of frame.actions) {
++cumulativeActionIndex;
if (!action.ready || this._recordingTreeOutline.getCachedTreeElement(action))
break;
this._createRecordingActionTreeElement(action, cumulativeActionIndex, folder);
}
});
}
_updateRecordNavigationItem()
{
if (!this._canvas || !(this._canvas.contextType === WI.Canvas.ContextType.Canvas2D || this._canvas.contextType === WI.Canvas.ContextType.WebGL || this._canvas.contextType === WI.Canvas.ContextType.WebGL2)) {
this._recordButtonNavigationItem.enabled = false;
return;
}
this._recordButtonNavigationItem.toggled = this._canvas.recordingActive;
this._recordButtonNavigationItem.label = this._recordButtonNavigationItem.toggled ? WI.UIString("Stop") : WI.UIString("Start");
}
_updateRecordingScopeBar()
{
if (this._scopeBar) {
this._placeholderScopeBarItem = null;
this._recordingNavigationBar.removeNavigationItem(this._scopeBar);
this._scopeBar = null;
}
this._recordingNavigationBar.element.classList.toggle("hidden", !this._canvas);
let hasRecordings = this._recording || (this._canvas && this._canvas.recordingCollection.size);
this.element.classList.toggle("has-recordings", hasRecordings);
this.element.classList.toggle("showing-recording", !!this._recording);
if (!hasRecordings)
return;
let scopeBarItems = [];
let selectedScopeBarItem = null;
let createScopeBarItem = (recording) => {
let scopeBarItem = new WI.ScopeBarItem(recording.displayName, recording.displayName);
if (recording === this._recording)
selectedScopeBarItem = scopeBarItem;
else
scopeBarItem.selected = false;
scopeBarItem.__recording = recording;
scopeBarItems.push(scopeBarItem);
};
if (this._canvas && this._canvas.recordingCollection) {
for (let recording of this._canvas.recordingCollection)
createScopeBarItem(recording);
}
if (this._recording && (!this._canvas || !this._canvas.recordingCollection.has(this._recording)))
createScopeBarItem(this._recording);
if (!selectedScopeBarItem) {
selectedScopeBarItem = scopeBarItems[0];
this._placeholderScopeBarItem = new WI.ScopeBarItem("canvas-recording-scope-bar-item-placeholder", WI.UIString("Recordings"), {exclusive: true, hidden: true});
this._placeholderScopeBarItem.selected = true;
scopeBarItems.unshift(this._placeholderScopeBarItem);
}
this._scopeBar = new WI.ScopeBar("canvas-recording-scope-bar", scopeBarItems, selectedScopeBarItem, true);
this._scopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._scopeBarSelectionChanged, this);
this._recordingNavigationBar.insertNavigationItem(this._scopeBar, 0);
}
_createRecordingFrameTreeElement(frame, index, parent)
{
let folder = new WI.FolderTreeElement(WI.UIString("Frame %d").format((index + 1).toLocaleString()), frame);
if (!isNaN(frame.duration)) {
const higherResolution = true;
folder.status = Number.secondsToString(frame.duration / 1000, higherResolution);
}
parent.appendChild(folder);
return folder;
}
_createRecordingActionTreeElement(action, index, parent)
{
let treeElement = new WI.RecordingActionTreeElement(action, index, this._recording.type);
parent.appendChild(treeElement);
if (parent instanceof WI.FolderTreeElement && parent.representedObject instanceof WI.RecordingFrame) {
if (action !== parent.representedObject.actions.lastValue) {
parent.addClassName("processing");
if (!(parent.subtitle instanceof HTMLProgressElement))
parent.subtitle = document.createElement("progress");
if (parent.statusElement)
parent.subtitle.style.setProperty("width", `calc(100% - ${parent.statusElement.offsetWidth + 4}px`);
parent.subtitle.value = parent.representedObject.actions.indexOf(action) / parent.representedObject.actions.length;
} else {
parent.removeClassName("processing");
if (parent.representedObject.incomplete)
parent.subtitle = WI.UIString("Incomplete");
else
parent.subtitle = "";
}
}
if (action === this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
this.action = action;
return treeElement;
}
_handleRecordingProcessedAction(event)
{
let {action, index} = event.data;
this._recordingTreeOutline.element.dataset.indent = Number.countDigits(index);
let isInitialStateAction = !index;
console.assert(isInitialStateAction || this._recordingTreeOutline.children.lastValue instanceof WI.FolderTreeElement, "There should be a WI.FolderTreeElement for the frame for this action.");
this._createRecordingActionTreeElement(action, index, isInitialStateAction ? this._recordingTreeOutline : this._recordingTreeOutline.children.lastValue);
if (!this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol]) {
console.assert(action === this._recording.actions[0]);
this.action = this._recording.actions[0];
}
if (this._recording.ready) {
this._recording.removeEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
this._recording.removeEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
if (this._recordingProcessingOptionsContainer) {
this._recordingProcessingOptionsContainer.remove();
this._recordingProcessingOptionsContainer = null;
}
}
}
_handleRecordingStartProcessingFrame(event)
{
let {frame, index} = event.data;
this._createRecordingFrameTreeElement(frame, index, this._recordingTreeOutline);
}
};
WI.CanvasSidebarPanel.SelectedActionSymbol = Symbol("selected-action");
+60
View File
@@ -0,0 +1,60 @@
/*
* 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.
*/
.content-view.tab.canvas .navigation-bar > .item .canvas-overview .icon {
content: url(../Images/CanvasOverview.svg);
}
.content-view.tab.canvas .navigation-bar > .item .canvas.canvas-2d .icon {
content: url(../Images/Canvas2D.svg);
}
.content-view.tab.canvas .navigation-bar > .item .canvas:matches(.webgl, .webgl2, .webgpu, .webmetal) .icon {
content: url(../Images/Canvas3D.svg);
}
.content-view.tab.canvas .navigation-bar > .item .recording > .icon {
content: url(../Images/Recording.svg);
}
.content-view.tab.canvas .navigation-bar > .item .shader-program > .icon {
content: image-set(url(../Images/DocumentGL.png) 1x, url(../Images/DocumentGL@2x.png) 2x);
}
@media (prefers-color-scheme: dark) {
.content-view.tab.canvas .navigation-bar > .item .canvas-overview .icon {
filter: invert(88%);
}
.content-view.tab.canvas .navigation-bar > .item .canvas .icon,
.content-view.tab.canvas .navigation-bar > .item .recording > .icon {
filter: invert();
}
.content-view.canvas > .preview > img,
.content-view.canvas .preview-container > canvas {
background-color: white;
}
}
+297
View File
@@ -0,0 +1,297 @@
/*
* 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.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTabContentView
{
constructor(representedObject)
{
console.assert(!representedObject || representedObject instanceof WI.Canvas);
let tabBarItem = WI.GeneralTabBarItem.fromTabInfo(WI.CanvasTabContentView.tabInfo());
const navigationSidebarPanelConstructor = WI.CanvasSidebarPanel;
const detailsSidebarPanelConstructors = [WI.RecordingStateDetailsSidebarPanel, WI.RecordingTraceDetailsSidebarPanel, WI.CanvasDetailsSidebarPanel];
const disableBackForward = true;
super("canvas", ["canvas"], tabBarItem, navigationSidebarPanelConstructor, detailsSidebarPanelConstructors, disableBackForward);
this._canvasCollection = new WI.CanvasCollection;
this._canvasTreeOutline = new WI.TreeOutline;
this._canvasTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._canvasTreeOutlineSelectionDidChange, this);
this._overviewTreeElement = new WI.GeneralTreeElement("canvas-overview", WI.UIString("Overview"), null, this._canvasCollection);
this._canvasTreeOutline.appendChild(this._overviewTreeElement);
this._savedRecordingsTreeElement = new WI.FolderTreeElement(WI.UIString("Saved Recordings"), WI.RecordingCollection);
this._savedRecordingsTreeElement.hidden = true;
this._overviewTreeElement.appendChild(this._savedRecordingsTreeElement);
this._recordShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Space, this._handleSpace.bind(this));
this._recordShortcut.implicitlyPreventsDefault = false;
this._recordShortcut.disabled = true;
this._recordSingleFrameShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift, WI.KeyboardShortcut.Key.Space, this._handleSpace.bind(this));
this._recordSingleFrameShortcut.implicitlyPreventsDefault = false;
this._recordSingleFrameShortcut.disabled = true;
WI.canvasManager.enable();
}
static tabInfo()
{
return {
image: "Images/Canvas.svg",
title: WI.UIString("Canvas"),
};
}
static isTabAllowed()
{
return !!window.CanvasAgent;
}
// Public
treeElementForRepresentedObject(representedObject)
{
return this._canvasTreeOutline.findTreeElement(representedObject);
}
get type()
{
return WI.CanvasTabContentView.Type;
}
get supportsSplitContentBrowser()
{
return true;
}
get managesNavigationSidebarPanel()
{
return true;
}
canShowRepresentedObject(representedObject)
{
return representedObject instanceof WI.Canvas
|| representedObject instanceof WI.CanvasCollection
|| representedObject instanceof WI.Recording
|| representedObject instanceof WI.ShaderProgram;
}
shown()
{
super.shown();
this._recordShortcut.disabled = false;
this._recordSingleFrameShortcut.disabled = false;
if (!this.contentBrowser.currentContentView)
this.showRepresentedObject(this._canvasCollection);
}
hidden()
{
this._recordShortcut.disabled = true;
this._recordSingleFrameShortcut.disabled = true;
super.hidden();
}
closed()
{
WI.canvasManager.disable();
super.closed();
}
restoreStateFromCookie(cookie)
{
// FIXME: implement once <https://webkit.org/b/177606> is complete.
}
saveStateToCookie(cookie)
{
// FIXME: implement once <https://webkit.org/b/177606> is complete.
}
async handleFileDrop(files)
{
await WI.FileUtilities.readJSON(files, (result) => WI.canvasManager.processJSON(result));
}
// Protected
attached()
{
super.attached();
WI.canvasManager.addEventListener(WI.CanvasManager.Event.CanvasAdded, this._handleCanvasAdded, this);
WI.canvasManager.addEventListener(WI.CanvasManager.Event.CanvasRemoved, this._handleCanvasRemoved, this);
WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingSaved, this._handleRecordingSavedOrStopped, this);
WI.Canvas.addEventListener(WI.Canvas.Event.RecordingStopped, this._handleRecordingSavedOrStopped, this);
let canvases = WI.canvasManager.canvases;
for (let canvas of this._canvasCollection) {
if (!canvases.includes(canvas))
this._removeCanvas(canvas);
}
for (let canvas of canvases) {
if (!this._canvasCollection.has(canvas))
this._addCanvas(canvas);
}
this._savedRecordingsTreeElement.removeChildren();
for (let recording of WI.canvasManager.savedRecordings)
this._addRecording(recording, {suppressShowRecording: true});
}
detached()
{
WI.Canvas.removeEventListener(null, null, this);
WI.canvasManager.removeEventListener(null, null, this);
super.detached();
}
// Private
_addCanvas(canvas)
{
this._overviewTreeElement.appendChild(new WI.CanvasTreeElement(canvas));
this._canvasCollection.add(canvas);
const options = {
suppressShowRecording: true,
};
for (let recording of canvas.recordingCollection)
this._addRecording(recording, options);
}
_removeCanvas(canvas)
{
let treeElement = this._canvasTreeOutline.findTreeElement(canvas);
console.assert(treeElement, "Missing tree element for canvas.", canvas);
const suppressNotification = true;
treeElement.deselect(suppressNotification);
this._overviewTreeElement.removeChild(treeElement);
this._canvasCollection.remove(canvas);
let currentContentView = this.contentBrowser.currentContentView;
if (currentContentView instanceof WI.CanvasContentView)
WI.showRepresentedObject(this._canvasCollection);
else if (currentContentView instanceof WI.RecordingContentView && canvas.recordingCollection.has(currentContentView.representedObject))
this.contentBrowser.updateHierarchicalPathForCurrentContentView();
let navigationSidebarPanel = this.navigationSidebarPanel;
if (navigationSidebarPanel instanceof WI.CanvasSidebarPanel && navigationSidebarPanel.visible)
navigationSidebarPanel.updateRepresentedObjects();
this.showDetailsSidebarPanels();
}
_addRecording(recording, options = {})
{
if (!recording.source) {
const subtitle = null;
let recordingTreeElement = new WI.GeneralTreeElement(["recording"], recording.displayName, subtitle, recording);
this._savedRecordingsTreeElement.hidden = false;
this._savedRecordingsTreeElement.appendChild(recordingTreeElement);
}
if (!options.suppressShowRecording)
this.showRepresentedObject(recording);
}
_handleCanvasAdded(event)
{
this._addCanvas(event.data.canvas);
}
_handleCanvasRemoved(event)
{
this._removeCanvas(event.data.canvas);
}
_canvasTreeOutlineSelectionDidChange(event)
{
let selectedElement = this._canvasTreeOutline.selectedTreeElement;
if (!selectedElement)
return;
let representedObject = selectedElement.representedObject;
if (!this.canShowRepresentedObject(representedObject)) {
console.assert(false, "Unexpected representedObject.", representedObject);
return;
}
this.showRepresentedObject(representedObject);
}
_handleRecordingSavedOrStopped(event)
{
let {recording, initiatedByUser, imported} = event.data;
if (!recording)
return;
let options = {};
// Always show imported recordings.
if (recording.source || !imported)
options.suppressShowRecording = !initiatedByUser || this.contentBrowser.currentRepresentedObjects.some((representedObject) => representedObject instanceof WI.Recording);
this._addRecording(recording, options);
}
_handleSpace(event)
{
if (WI.isEventTargetAnEditableField(event))
return;
if (!this.navigationSidebarPanel)
return;
let canvas = this.navigationSidebarPanel.canvas;
if (!canvas)
return;
if (canvas.recordingActive)
canvas.stopRecording();
else {
let singleFrame = !!event.shiftKey;
canvas.startRecording(singleFrame);
}
event.preventDefault();
}
};
WI.CanvasTabContentView.Type = "canvas";
+149
View File
@@ -0,0 +1,149 @@
/*
* 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.CanvasTreeElement = class CanvasTreeElement extends WI.FolderizedTreeElement
{
constructor(representedObject, showRecordings = true)
{
console.assert(representedObject instanceof WI.Canvas);
let subtitle = WI.Canvas.displayNameForContextType(representedObject.contextType);
super(["canvas", representedObject.contextType], representedObject.displayName, subtitle, representedObject);
this.registerFolderizeSettings("shader-programs", WI.UIString("Shader Programs"), this.representedObject.shaderProgramCollection, WI.ShaderProgramTreeElement);
this.representedObject.addEventListener(WI.Canvas.Event.RecordingStarted, this._updateStatus, this);
this.representedObject.addEventListener(WI.Canvas.Event.RecordingStopped, this._updateStatus, this);
this.representedObject.shaderProgramCollection.addEventListener(WI.Collection.Event.ItemAdded, this._handleItemAdded, this);
this.representedObject.shaderProgramCollection.addEventListener(WI.Collection.Event.ItemRemoved, this._handleItemRemoved, this);
this._showRecordings = showRecordings;
if (this._showRecordings) {
function createRecordingTreeElement(recording) {
return new WI.GeneralTreeElement(["recording"], recording.displayName, null, recording);
}
this.registerFolderizeSettings("recordings", WI.UIString("Recordings"), this.representedObject.recordingCollection, createRecordingTreeElement);
this.representedObject.recordingCollection.addEventListener(WI.Collection.Event.ItemAdded, this._handleItemAdded, this);
this.representedObject.recordingCollection.addEventListener(WI.Collection.Event.ItemRemoved, this._handleItemRemoved, this);
}
}
// Protected
onattach()
{
super.onattach();
this.element.addEventListener("mouseover", this._handleMouseOver.bind(this));
this.element.addEventListener("mouseout", this._handleMouseOut.bind(this));
this.onpopulate();
}
onpopulate()
{
super.onpopulate();
if (this.children.length && !this.shouldRefreshChildren)
return;
this.shouldRefreshChildren = false;
this.removeChildren();
for (let program of this.representedObject.shaderProgramCollection)
this.addChildForRepresentedObject(program);
if (this._showRecordings) {
for (let recording of this.representedObject.recordingCollection)
this.addChildForRepresentedObject(recording);
}
}
populateContextMenu(contextMenu, event)
{
super.populateContextMenu(contextMenu, event);
contextMenu.appendItem(WI.UIString("Log Canvas Context"), () => {
WI.RemoteObject.resolveCanvasContext(this.representedObject, WI.RuntimeManager.ConsoleObjectGroup, (remoteObject) => {
if (!remoteObject)
return;
const text = WI.UIString("Selected Canvas Context");
WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, {addSpecialUserLogClass: true});
});
});
contextMenu.appendSeparator();
}
// Private
_handleItemAdded(event)
{
this.addChildForRepresentedObject(event.data.item);
}
_handleItemRemoved(event)
{
this.removeChildForRepresentedObject(event.data.item);
}
_handleMouseOver(event)
{
if (this.representedObject.cssCanvasName || this.representedObject.contextType === WI.Canvas.ContextType.WebGPU) {
this.representedObject.requestClientNodes((clientNodes) => {
WI.domManager.highlightDOMNodeList(clientNodes);
});
} else {
this.representedObject.requestNode((node) => {
if (!node || !node.ownerDocument)
return;
node.highlight();
});
}
}
_handleMouseOut(event)
{
WI.domManager.hideDOMNodeHighlight();
}
_updateStatus()
{
if (this.representedObject.recordingActive) {
if (!this.status || !this.status.__showingSpinner) {
let spinner = new WI.IndeterminateProgressSpinner;
this.status = spinner.element;
this.status.__showingSpinner = true;
}
} else {
if (this.status && this.status.__showingSpinner)
this.status = "";
}
}
};
@@ -0,0 +1,108 @@
/*
* 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.
*/
.sidebar > .panel.changes-panel {
white-space: pre-wrap;
overflow-y: auto;
}
.sidebar > .panel.changes-panel .css-rule {
padding: var(--css-declaration-vertical-padding) 0;
font: 11px Menlo, monospace;
color: var(--text-color-secondary);
background-color: var(--background-color-content);
-webkit-user-select: text;
}
.sidebar > .panel.changes-panel .css-rule + .css-rule {
border-top: 0.5px solid var(--text-color-quaternary);
}
.sidebar > .panel.selected.changes-panel.empty {
display: flex;
justify-content: center;
align-items: center;
}
.changes-panel .resource-section {
border-bottom: 0.5px solid var(--text-color-quaternary);
}
.changes-panel .resource-section > .header {
position: sticky;
top: 0;
z-index: var(--z-index-header);
padding: 4px 8px;
background-color: var(--panel-background-color);
border-bottom: 0.5px solid var(--text-color-quaternary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.changes-panel .resource-section > .header > a:hover {
color: var(--text-color);
}
.sidebar > .panel.changes-panel .selector-line,
.sidebar > .panel.changes-panel .close-brace {
padding-left: var(--css-declaration-horizontal-padding);
}
.changes-panel .selector.style-attribute {
font: 12px sans-serif;
}
.changes-panel .selector:not(.style-attribute) {
color: var(--text-color);
}
.changes-panel .css-property-line > .property {
display: inline;
}
.changes-panel .css-property-line.added {
color: var(--diff-addition-text-color);
background-color: var(--diff-addition-background-color);
}
.changes-panel .css-property-line.removed {
color: var(--diff-deletion-text-color);
background-color: var(--diff-deletion-background-color);
}
.changes-panel .css-property-line.removed::before {
content: "-";
position: absolute;
left: var(--css-declaration-horizontal-padding);
pointer-events: none;
}
.changes-panel .css-property-line.added::before {
content: "+";
position: absolute;
left: var(--css-declaration-horizontal-padding);
pointer-events: none;
}
@@ -0,0 +1,189 @@
/*
* 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.ChangesDetailsSidebarPanel = class ChangesDetailsSidebarPanel extends WI.DOMDetailsSidebarPanel
{
constructor()
{
super("changes-details", WI.UIString("Changes"));
this.element.classList.add("changes-panel");
this.element.dir = "ltr";
}
// Public
supportsDOMNode(nodeToInspect)
{
return nodeToInspect.nodeType() === Node.ELEMENT_NODE;
}
attached()
{
super.attached();
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.CSSManager.addEventListener(WI.CSSManager.Event.ModifiedStylesChanged, this.needsLayout, this);
}
detached()
{
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.CSSManager.removeEventListener(WI.CSSManager.Event.ModifiedStylesChanged, this.needsLayout, this);
super.detached();
}
// Protected
layout()
{
super.layout();
this.element.removeChildren();
let modifiedStyles = WI.cssManager.modifiedStyles;
if (WI.settings.cssChangesPerNode.value) {
if (this.domNode) {
let stylesForNode = WI.cssManager.stylesForNode(this.domNode);
modifiedStyles = modifiedStyles.filter((style) => {
if (style.node === this.domNode)
return true;
if (style.ownerRule)
return stylesForNode.matchedRules.some((matchedRule) => style.ownerRule.isEqualTo(matchedRule));
return false;
});
} else
modifiedStyles = [];
}
this.element.classList.toggle("empty", !modifiedStyles.length);
if (!modifiedStyles.length) {
this.element.textContent = WI.UIString("No CSS Changes");
return;
}
let declarationsForStyleSheet = new Map();
for (let style of modifiedStyles) {
let styleDeclarations = declarationsForStyleSheet.get(style.ownerStyleSheet);
if (!styleDeclarations) {
styleDeclarations = [];
declarationsForStyleSheet.set(style.ownerStyleSheet, styleDeclarations);
}
styleDeclarations.push(style);
}
for (let [styleSheet, styles] of declarationsForStyleSheet) {
let resourceSection = this.element.appendChild(document.createElement("section"));
resourceSection.classList.add("resource-section");
let resourceHeader = resourceSection.appendChild(document.createElement("div"));
resourceHeader.classList.add("header");
resourceHeader.append(styleSheet.isInlineStyleAttributeStyleSheet() ? styles[0].selectorText : this._createLocationLink(styleSheet));
for (let style of styles)
resourceSection.append(this._createRuleElement(style));
}
}
// Private
_createRuleElement(style)
{
let ruleElement = document.createElement("div");
ruleElement.classList.add("css-rule");
let selectorLineElement = ruleElement.appendChild(document.createElement("div"));
selectorLineElement.className = "selector-line";
let selectorElement = selectorLineElement.appendChild(document.createElement("span"));
selectorElement.className = "selector";
if (style.type === WI.CSSStyleDeclaration.Type.Inline) {
selectorElement.textContent = WI.UIString("Style Attribute");
selectorElement.classList.add("style-attribute");
} else
selectorElement.textContent = style.ownerRule.selectorText;
selectorLineElement.append(" {\n");
function onEach(cssProperty, action) {
let className = "";
if (action === 1)
className = "added";
else if (action === -1)
className = "removed";
else
className = "unchanged";
let propertyLineElement = ruleElement.appendChild(document.createElement("div"));
propertyLineElement.classList.add("css-property-line", className);
const delegate = null;
let stylePropertyView = new WI.SpreadsheetStyleProperty(delegate, cssProperty, {readOnly: true, hideDocumentation: true});
propertyLineElement.append(WI.indentString(), stylePropertyView.element, "\n");
}
function comparator(a, b) {
return a.equals(b);
}
Array.diffArrays(style.initialState.visibleProperties, style.visibleProperties, onEach, comparator);
let closeBraceElement = document.createElement("span");
closeBraceElement.className = "close-brace";
closeBraceElement.textContent = "}";
ruleElement.append(closeBraceElement, "\n");
return ruleElement;
}
_createLocationLink(styleSheet)
{
const options = {
nameStyle: WI.SourceCodeLocation.NameStyle.Short,
columnStyle: WI.SourceCodeLocation.ColumnStyle.Hidden,
dontFloat: true,
ignoreNetworkTab: true,
ignoreSearchTab: true,
};
const lineNumber = 0;
const columnNumber = 0;
let sourceCodeLocation = styleSheet.createSourceCodeLocation(lineNumber, columnNumber);
return WI.createSourceCodeLocationLink(sourceCodeLocation, options);
}
_mainResourceDidChange(event)
{
if (!event.target.isMainFrame())
return;
this.needsLayout();
}
};
+105
View File
@@ -0,0 +1,105 @@
/*
* 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.
*/
.details-section > .content > .group > .row.chart > .title {
color: hsl(0, 0%, 22%);
white-space: nowrap;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
font-size: 11px;
padding: 4px 5px 0px 9px;
}
.details-section > .content > .group > .row.chart > .chart-content {
display: flex;
justify-content: center;
padding: 8px 5px 0px 12px;
}
.details-section > .content > .group > .row.chart > .chart-content > svg > path.hidden {
display: none;
}
.details-section > .content > .group > .row.chart > .chart-content > svg > path.chart-segment {
stroke-width: 1;
stroke: rgba(255, 255, 255, 0.7);
}
.details-section > .content > .group > .row.chart > .chart-content > svg > path.empty-chart {
fill: rgba(0, 0, 0, 0.02);
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.1);
}
.details-section > .content > .group > .row.chart > .chart-content > .legend {
display: table;
margin-left: 12px;
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item {
display: table-row;
height: 16px;
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label {
color: hsl(0, 0%, 20%);
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > .value {
padding-left: 8px;
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > * {
display: table-cell;
vertical-align: middle;
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > .color-key,
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox] {
width: 10px;
height: 10px;
vertical-align: top;
margin: 0 8px 0 3px;
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > .color-key {
display: inline-block;
margin-top: 1px;
border: solid 1px hsla(0, 0%, 0%, 0.15);
border-radius: 2px;
}
.details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox]:not(:checked),
body.window-docked-inactive .details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox],
body.window-inactive .details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox] {
filter: none !important;
}
.details-section > .content > .group > .row.chart > .defs-only {
display: none;
}
+392
View File
@@ -0,0 +1,392 @@
/*
* 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.ChartDetailsSectionRow = class ChartDetailsSectionRow extends WI.DetailsSectionRow
{
constructor(delegate, chartSize, innerRadiusRatio)
{
super(WI.UIString("No Chart Available"));
innerRadiusRatio = innerRadiusRatio || 0;
console.assert(chartSize > 0, chartSize);
console.assert(innerRadiusRatio >= 0 && innerRadiusRatio < 1, innerRadiusRatio);
this.element.classList.add("chart");
this._titleElement = document.createElement("div");
this._titleElement.className = "title";
this.element.appendChild(this._titleElement);
let chartContentElement = document.createElement("div");
chartContentElement.className = "chart-content";
this.element.appendChild(chartContentElement);
this._chartElement = createSVGElement("svg");
chartContentElement.appendChild(this._chartElement);
this._legendElement = document.createElement("div");
this._legendElement.className = "legend";
chartContentElement.appendChild(this._legendElement);
this._delegate = delegate;
this._items = new Map;
this._title = "";
this._chartSize = chartSize;
this._radius = (this._chartSize / 2) - 1; // Subtract one to accomodate chart stroke width.
this._innerRadius = innerRadiusRatio ? Math.floor(this._radius * innerRadiusRatio) : 0;
this._total = 0;
this._svgFiltersElement = document.createElement("svg");
this._svgFiltersElement.classList.add("defs-only");
this.element.append(this._svgFiltersElement);
this._checkboxStyleElement = document.createElement("style");
this._checkboxStyleElement.id = "checkbox-styles";
document.getElementsByTagName("head")[0].append(this._checkboxStyleElement);
function createEmptyChartPathData(c, r1, r2)
{
const a1 = 0;
const a2 = Math.PI * 1.9999;
let x1 = c + Math.cos(a1) * r1,
y1 = c + Math.sin(a1) * r1,
x2 = c + Math.cos(a2) * r1,
y2 = c + Math.sin(a2) * r1,
x3 = c + Math.cos(a2) * r2,
y3 = c + Math.sin(a2) * r2,
x4 = c + Math.cos(a1) * r2,
y4 = c + Math.sin(a1) * r2;
return [
"M", x1, y1, // Starting position.
"A", r1, r1, 0, 1, 1, x2, y2, // Draw outer arc.
"Z", // Close path.
"M", x3, y3, // Starting position.
"A", r2, r2, 0, 1, 0, x4, y4, // Draw inner arc.
"Z" // Close path.
].join(" ");
}
this._emptyChartPath = createSVGElement("path");
this._emptyChartPath.setAttribute("d", createEmptyChartPathData(this._chartSize / 2, this._radius, this._innerRadius));
this._emptyChartPath.classList.add("empty-chart");
this._chartElement.appendChild(this._emptyChartPath);
}
// Public
get chartSize()
{
return this._chartSize;
}
set title(title)
{
if (this._title === title)
return;
this._title = title;
this._titleElement.textContent = title;
}
get total()
{
return this._total;
}
addItem(id, label, value, color, checkbox, checked)
{
console.assert(!this._items.has(id), "Already added item with id: " + id);
if (this._items.has(id))
return;
console.assert(value >= 0, "Value cannot be negative.");
if (value < 0)
return;
this._items.set(id, {label, value, color, checkbox, checked});
this._total += value;
this._needsLayout();
}
setItemValue(id, value)
{
let item = this._items.get(id);
console.assert(item, "Cannot set value for invalid item id: " + id);
if (!item)
return;
console.assert(value >= 0, "Value cannot be negative.");
if (value < 0)
return;
if (item.value === value)
return;
this._total += value - item.value;
item.value = value;
this._needsLayout();
}
clearItems()
{
for (let item of this._items.values()) {
let path = item[WI.ChartDetailsSectionRow.ChartSegmentPathSymbol];
if (path)
path.remove();
}
this._total = 0;
this._items.clear();
this._needsLayout();
}
// Private
_addCheckboxColorFilter(id, r, g, b)
{
for (let i = 0; i < this._svgFiltersElement.childNodes.length; ++i) {
if (this._svgFiltersElement.childNodes[i].id === id)
return;
}
r /= 255;
b /= 255;
g /= 255;
// Create an svg:filter element that approximates "background-blend-mode: color", for grayscale input.
let filterElement = createSVGElement("filter");
filterElement.id = id;
filterElement.setAttribute("color-interpolation-filters", "sRGB");
let values = [1 - r, 0, 0, 0, r,
1 - g, 0, 0, 0, g,
1 - b, 0, 0, 0, b,
0, 0, 0, 1, 0];
let colorMatrixPrimitive = createSVGElement("feColorMatrix");
colorMatrixPrimitive.setAttribute("type", "matrix");
colorMatrixPrimitive.setAttribute("values", values.join(" "));
function createGammaPrimitive(tagName, value)
{
let gammaPrimitive = createSVGElement(tagName);
gammaPrimitive.setAttribute("type", "gamma");
gammaPrimitive.setAttribute("exponent", value);
return gammaPrimitive;
}
let componentTransferPrimitive = createSVGElement("feComponentTransfer");
componentTransferPrimitive.append(createGammaPrimitive("feFuncR", 1.4), createGammaPrimitive("feFuncG", 1.4), createGammaPrimitive("feFuncB", 1.4));
filterElement.append(colorMatrixPrimitive, componentTransferPrimitive);
this._svgFiltersElement.append(filterElement);
let styleSheet = this._checkboxStyleElement.sheet;
styleSheet.insertRule(".details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox]." + id + " { filter: grayscale(1) url(#" + id + ") }", 0);
}
_updateLegend()
{
if (!this._items.size) {
this._legendElement.removeChildren();
return;
}
function formatItemValue(item)
{
if (this._delegate && typeof this._delegate.formatChartValue === "function")
return this._delegate.formatChartValue(item.value);
return item.value;
}
for (let [id, item] of this._items) {
if (item[WI.ChartDetailsSectionRow.LegendItemValueElementSymbol]) {
let valueElement = item[WI.ChartDetailsSectionRow.LegendItemValueElementSymbol];
valueElement.textContent = formatItemValue.call(this, item);
continue;
}
let labelElement = document.createElement("label");
let keyElement;
if (item.checkbox) {
let className = id.toLowerCase();
let rgb = item.color.substring(4, item.color.length - 1).replace(/ /g, "").split(",");
if (rgb[0] === rgb[1] && rgb[1] === rgb[2])
rgb[0] = rgb[1] = rgb[2] = Math.min(160, rgb[0]);
keyElement = document.createElement("input");
keyElement.type = "checkbox";
keyElement.classList.add(className);
keyElement.checked = item.checked;
keyElement[WI.ChartDetailsSectionRow.DataItemIdSymbol] = id;
keyElement.addEventListener("change", this._legendItemCheckboxValueChanged.bind(this));
this._addCheckboxColorFilter(className, rgb[0], rgb[1], rgb[2]);
} else {
keyElement = document.createElement("div");
keyElement.classList.add("color-key");
keyElement.style.backgroundColor = item.color;
}
labelElement.append(keyElement, item.label);
let valueElement = document.createElement("div");
valueElement.classList.add("value");
valueElement.textContent = formatItemValue.call(this, item);
item[WI.ChartDetailsSectionRow.LegendItemValueElementSymbol] = valueElement;
let legendItemElement = document.createElement("div");
legendItemElement.classList.add("legend-item");
legendItemElement.append(labelElement, valueElement);
this._legendElement.append(legendItemElement);
}
}
_legendItemCheckboxValueChanged(event)
{
let checkbox = event.target;
let id = checkbox[WI.ChartDetailsSectionRow.DataItemIdSymbol];
this.dispatchEventToListeners(WI.ChartDetailsSectionRow.Event.LegendItemChecked, {id, checked: checkbox.checked});
}
_needsLayout()
{
if (this._scheduledLayoutUpdateIdentifier)
return;
this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this._updateLayout.bind(this));
}
_updateLayout()
{
if (this._scheduledLayoutUpdateIdentifier) {
cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
this._scheduledLayoutUpdateIdentifier = undefined;
}
this._updateLegend();
this._chartElement.setAttribute("width", this._chartSize);
this._chartElement.setAttribute("height", this._chartSize);
this._chartElement.setAttribute("viewbox", "0 0 " + this._chartSize + " " + this._chartSize);
function createSegmentPathData(c, a1, a2, r1, r2)
{
const largeArcFlag = ((a2 - a1) % (Math.PI * 2)) > Math.PI ? 1 : 0;
let x1 = c + Math.cos(a1) * r1,
y1 = c + Math.sin(a1) * r1,
x2 = c + Math.cos(a2) * r1,
y2 = c + Math.sin(a2) * r1,
x3 = c + Math.cos(a2) * r2,
y3 = c + Math.sin(a2) * r2,
x4 = c + Math.cos(a1) * r2,
y4 = c + Math.sin(a1) * r2;
return [
"M", x1, y1, // Starting position.
"A", r1, r1, 0, largeArcFlag, 1, x2, y2, // Draw outer arc.
"L", x3, y3, // Connect outer and inner arcs.
"A", r2, r2, 0, largeArcFlag, 0, x4, y4, // Draw inner arc.
"Z" // Close path.
].join(" ");
}
// Balance item values so that all non-zero chart segments are visible.
const minimumDisplayValue = this._total * 0.015;
let items = [];
for (let item of this._items.values()) {
item.displayValue = item.value ? Math.max(minimumDisplayValue, item.value) : 0;
if (item.displayValue)
items.push(item);
}
if (items.length > 1) {
items.sort(function(a, b) { return a.value - b.value; });
let largeItemCount = items.length;
let totalAdjustedValue = 0;
for (let item of items) {
if (item.value < minimumDisplayValue) {
totalAdjustedValue += minimumDisplayValue - item.value;
largeItemCount--;
continue;
}
if (!totalAdjustedValue || !largeItemCount)
break;
const donatedValue = totalAdjustedValue / largeItemCount;
if (item.displayValue - donatedValue >= minimumDisplayValue) {
item.displayValue -= donatedValue;
totalAdjustedValue -= donatedValue;
}
largeItemCount--;
}
}
const center = this._chartSize / 2;
let startAngle = -Math.PI / 2;
let endAngle = 0;
for (let [id, item] of this._items) {
let path = item[WI.ChartDetailsSectionRow.ChartSegmentPathSymbol];
if (!path) {
path = createSVGElement("path");
path.classList.add("chart-segment");
path.setAttribute("fill", item.color);
this._chartElement.appendChild(path);
item[WI.ChartDetailsSectionRow.ChartSegmentPathSymbol] = path;
}
if (!item.value) {
path.classList.add("hidden");
continue;
}
const angle = (item.displayValue / this._total) * Math.PI * 2;
endAngle = startAngle + angle;
path.setAttribute("d", createSegmentPathData(center, startAngle, endAngle, this._radius, this._innerRadius));
path.classList.remove("hidden");
startAngle = endAngle;
}
}
};
WI.ChartDetailsSectionRow.DataItemIdSymbol = Symbol("chart-details-section-row-data-item-id");
WI.ChartDetailsSectionRow.ChartSegmentPathSymbol = Symbol("chart-details-section-row-chart-segment-path");
WI.ChartDetailsSectionRow.LegendItemValueElementSymbol = Symbol("chart-details-section-row-legend-item-value-element");
WI.ChartDetailsSectionRow.Event = {
LegendItemChecked: "chart-details-section-row-legend-item-checked"
};
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2017-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.
*/
.navigation-bar .item.checkbox {
margin: 0;
padding: 1px 8px 3px;
}
.navigation-bar .item.checkbox label {
padding-inline-start: 3px;
}
.navigation-bar .item.checkbox input[type=checkbox] {
position: relative;
top: 1px;
}
+89
View File
@@ -0,0 +1,89 @@
/*
* 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.CheckboxNavigationItem = class CheckboxNavigationItem extends WI.NavigationItem
{
constructor(identifier, label, checked)
{
super(identifier, "checkbox");
this._checkboxElement = this.element.appendChild(document.createElement("input"));
this._checkboxElement.checked = checked;
this._checkboxElement.id = "checkbox-navigation-item-" + identifier;
this._checkboxElement.type = "checkbox";
this._checkboxElement.addEventListener("change", this._checkboxChanged.bind(this));
this._checkboxLabel = this.element.appendChild(document.createElement("label"));
this._checkboxLabel.className = "toggle";
this._checkboxLabel.setAttribute("for", this._checkboxElement.id);
this._checkboxLabel.addEventListener("click", this._handleLabelClick.bind(this));
this.label = label;
}
// Public
get checked()
{
return this._checkboxElement.checked;
}
set checked(flag)
{
this._checkboxElement.checked = flag;
}
set label(label)
{
this._checkboxLabel.removeChildren();
if (label)
this._checkboxLabel.append(label);
}
// Protected
get additionalClassNames()
{
return ["checkbox", "button"];
}
// Private
_checkboxChanged(event)
{
this.dispatchEventToListeners(WI.CheckboxNavigationItem.Event.CheckedDidChange);
}
_handleLabelClick(event)
{
if (WI.isEventTargetAnEditableField(event))
event.stop();
}
};
WI.CheckboxNavigationItem.Event = {
CheckedDidChange: "checkbox-navigation-item-checked-did-change",
};
+41
View File
@@ -0,0 +1,41 @@
/*
* 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.
*/
.circle-chart {
position: relative;
}
.circle-chart > svg > path.background {
fill: hsla(0, 0%, 0%, 0.02);
stroke: hsla(0, 0%, var(--foreground-lightness), 0.1);
stroke-width: 1;
}
.circle-chart > .center {
position: absolute;
display: flex;
justify-content: center;
text-align: center;
}
+209
View File
@@ -0,0 +1,209 @@
/*
* 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.
*/
// CircleChart creates a donut/pie chart of colored sections.
//
// Initialize the chart with a size and inner radius to get a blank chart.
// To populate with data, first initialize the segments. The class names you
// provide for the segments will allow you to style them. You can then update
// the chart with new values (in the same order as the segments) at any time.
//
// SVG:
//
// - There is a single background path for the background.
// - There is a path for each segment.
// - If you want to put something inside the middle of the chart you can use `centerElement`.
//
// <div class="circle-chart">
// <svg width="120" height="120" viewBox="0 0 120 120">
// <path class="background" d="..."/>
// <path class="segment segment-class-name-1" d="..."/>
// <path class="segment segment-class-name-2" d="..."/>
// ...
// </svg>
// <div class="center"></div>
// </div>
WI.CircleChart = class CircleChart extends WI.View
{
constructor({size, innerRadiusRatio})
{
super();
this._data = [];
this._size = size;
this._radius = (size / 2) - 1;
this._innerRadius = innerRadiusRatio ? Math.floor(this._radius * innerRadiusRatio) : 0;
this.element.classList.add("circle-chart");
this._chartElement = this.element.appendChild(createSVGElement("svg"));
this._chartElement.setAttribute("width", size);
this._chartElement.setAttribute("height", size);
this._chartElement.setAttribute("viewBox", `0 0 ${size} ${size}`);
this._pathElements = [];
this._values = [];
this._total = 0;
let backgroundPath = this._chartElement.appendChild(createSVGElement("path"));
backgroundPath.setAttribute("d", this._createCompleteCirclePathData(this._size / 2, this._radius, this._innerRadius));
backgroundPath.classList.add("background");
}
// Public
get size() { return this._size; }
get centerElement()
{
if (!this._centerElement) {
this._centerElement = this.element.appendChild(document.createElement("div"));
this._centerElement.classList.add("center");
this._centerElement.style.width = this._centerElement.style.height = this._radius + "px";
this._centerElement.style.top = this._centerElement.style.left = (this._radius - this._innerRadius) + "px";
}
return this._centerElement;
}
get segments()
{
return this._segments;
}
set segments(segmentClassNames)
{
for (let pathElement of this._pathElements)
pathElement.remove();
this._pathElements = [];
for (let className of segmentClassNames) {
let pathElement = this._chartElement.appendChild(createSVGElement("path"));
pathElement.classList.add("segment", className);
this._pathElements.push(pathElement);
}
}
get values()
{
return this._values;
}
set values(values)
{
console.assert(!values.length || values.length === this._pathElements.length, "Should have the same number of values as segments");
this._values = values;
this._total = 0;
for (let value of values)
this._total += value;
}
clear()
{
this.values = new Array(this._values.length).fill(0);
}
// Protected
layout()
{
super.layout();
if (this.layoutReason === WI.View.LayoutReason.Resize)
return;
if (!this._values.length)
return;
const center = this._size / 2;
let startAngle = -Math.PI / 2;
let endAngle = 0;
for (let i = 0; i < this._values.length; ++i) {
let value = this._values[i];
let pathElement = this._pathElements[i];
if (value === 0)
pathElement.removeAttribute("d");
else if (value === this._total)
pathElement.setAttribute("d", this._createCompleteCirclePathData(center, this._radius, this._innerRadius));
else {
let angle = (value / this._total) * Math.PI * 2;
endAngle = startAngle + angle;
pathElement.setAttribute("d", this._createSegmentPathData(center, startAngle, endAngle, this._radius, this._innerRadius));
startAngle = endAngle;
}
}
}
// Private
_createCompleteCirclePathData(c, r1, r2)
{
const a1 = 0;
const a2 = Math.PI * 1.9999;
let x1 = c + Math.cos(a1) * r1,
y1 = c + Math.sin(a1) * r1,
x2 = c + Math.cos(a2) * r1,
y2 = c + Math.sin(a2) * r1,
x3 = c + Math.cos(a2) * r2,
y3 = c + Math.sin(a2) * r2,
x4 = c + Math.cos(a1) * r2,
y4 = c + Math.sin(a1) * r2;
return [
"M", x1, y1, // Starting position.
"A", r1, r1, 0, 1, 1, x2, y2, // Draw outer arc.
"Z", // Close path.
"M", x3, y3, // Starting position.
"A", r2, r2, 0, 1, 0, x4, y4, // Draw inner arc.
"Z" // Close path.
].join(" ");
}
_createSegmentPathData(c, a1, a2, r1, r2)
{
const largeArcFlag = ((a2 - a1) % (Math.PI * 2)) > Math.PI ? 1 : 0;
let x1 = c + Math.cos(a1) * r1,
y1 = c + Math.sin(a1) * r1,
x2 = c + Math.cos(a2) * r1,
y2 = c + Math.sin(a2) * r1,
x3 = c + Math.cos(a2) * r2,
y3 = c + Math.sin(a2) * r2,
x4 = c + Math.cos(a1) * r2,
y4 = c + Math.sin(a1) * r2;
return [
"M", x1, y1, // Starting position.
"A", r1, r1, 0, largeArcFlag, 1, x2, y2, // Draw outer arc.
"L", x3, y3, // Connect outer and inner arcs.
"A", r2, r2, 0, largeArcFlag, 0, x4, y4, // Draw inner arc.
"Z" // Close path.
].join(" ");
}
};
+32
View File
@@ -0,0 +1,32 @@
/*
* 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.
*/
.content-view.cluster > .content-view-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
+255
View File
@@ -0,0 +1,255 @@
/*
* Copyright (C) 2013, 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.ClusterContentView = class ClusterContentView extends WI.ContentView
{
constructor(representedObject)
{
super(representedObject);
this.element.classList.add("cluster");
this._contentViewContainer = new WI.ContentViewContainer;
this._contentViewContainer.addEventListener(WI.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
this.addSubview(this._contentViewContainer);
WI.ContentView.addEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
WI.ContentView.addEventListener(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
WI.ContentView.addEventListener(WI.ContentView.Event.NumberOfSearchResultsDidChange, this._contentViewNumberOfSearchResultsDidChange, this);
}
// Public
get navigationItems()
{
var currentContentView = this._contentViewContainer.currentContentView;
return currentContentView ? currentContentView.navigationItems : [];
}
get contentViewContainer()
{
return this._contentViewContainer;
}
get supportsSplitContentBrowser()
{
if (this._contentViewContainer.currentContentView)
return this._contentViewContainer.currentContentView.supportsSplitContentBrowser;
return super.supportsSplitContentBrowser;
}
get shouldSaveStateWhenHidden()
{
return true;
}
closed()
{
super.closed();
this._contentViewContainer.closeAllContentViews();
WI.ContentView.removeEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
WI.ContentView.removeEventListener(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
WI.ContentView.removeEventListener(WI.ContentView.Event.NumberOfSearchResultsDidChange, this._contentViewNumberOfSearchResultsDidChange, this);
}
canGoBack()
{
return this._contentViewContainer.canGoBack();
}
canGoForward()
{
return this._contentViewContainer.canGoForward();
}
goBack()
{
this._contentViewContainer.goBack();
}
goForward()
{
this._contentViewContainer.goForward();
}
get scrollableElements()
{
if (!this._contentViewContainer.currentContentView)
return [];
return this._contentViewContainer.currentContentView.scrollableElements;
}
get selectionPathComponents()
{
if (!this._contentViewContainer.currentContentView)
return [];
return this._contentViewContainer.currentContentView.selectionPathComponents;
}
get supplementalRepresentedObjects()
{
if (!this._contentViewContainer.currentContentView)
return [];
return this._contentViewContainer.currentContentView.supplementalRepresentedObjects;
}
get handleCopyEvent()
{
var currentContentView = this._contentViewContainer.currentContentView;
return currentContentView && typeof currentContentView.handleCopyEvent === "function" ? currentContentView.handleCopyEvent.bind(currentContentView) : null;
}
get supportsSave()
{
return !!this._contentViewContainer.currentContentView?.supportsSave;
}
get saveMode()
{
console.assert(this.supportsSave);
return this._contentViewContainer.currentContentView?.saveMode;
}
get saveData()
{
console.assert(this.supportsSave);
return this._contentViewContainer.currentContentView?.saveData;
}
get supportsSearch()
{
// Always return true so we can intercept the search query to resend it when switching content views.
return true;
}
get numberOfSearchResults()
{
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return null;
return currentContentView.numberOfSearchResults;
}
get hasPerformedSearch()
{
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return false;
return currentContentView.hasPerformedSearch;
}
set automaticallyRevealFirstSearchResult(reveal)
{
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return;
currentContentView.automaticallyRevealFirstSearchResult = reveal;
}
performSearch(query)
{
this._searchQuery = query;
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return;
currentContentView.performSearch(query);
}
searchCleared()
{
this._searchQuery = null;
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return;
currentContentView.searchCleared();
}
searchQueryWithSelection()
{
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return null;
return currentContentView.searchQueryWithSelection();
}
revealPreviousSearchResult(changeFocus)
{
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return;
currentContentView.revealPreviousSearchResult(changeFocus);
}
revealNextSearchResult(changeFocus)
{
var currentContentView = this._contentViewContainer.currentContentView;
if (!currentContentView || !currentContentView.supportsSearch)
return;
currentContentView.revealNextSearchResult(changeFocus);
}
// Private
_currentContentViewDidChange(event)
{
var currentContentView = this._contentViewContainer.currentContentView;
if (currentContentView && currentContentView.supportsSearch) {
if (this._searchQuery)
currentContentView.performSearch(this._searchQuery);
else
currentContentView.searchCleared();
}
this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange);
this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
}
_contentViewSelectionPathComponentDidChange(event)
{
if (event.target !== this._contentViewContainer.currentContentView)
return;
this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
}
_contentViewSupplementalRepresentedObjectsDidChange(event)
{
if (event.target !== this._contentViewContainer.currentContentView)
return;
this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange);
}
_contentViewNumberOfSearchResultsDidChange(event)
{
if (event.target !== this._contentViewContainer.currentContentView)
return;
this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange);
}
};
+758
View File
@@ -0,0 +1,758 @@
/*
* 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.
*/
(function () {
// By default CodeMirror defines syntax highlighting styles based on token
// only and shared styles between modes. This limiting and does not match
// what we have done in the Web Inspector. So this modifies the XML, CSS
// and JavaScript modes to supply two styles for each token. One for the
// token and one with the mode name.
function tokenizeLinkString(stream, state)
{
console.assert(state._linkQuoteCharacter !== undefined);
// Eat the string until the same quote is found that started the string.
// If this is unquoted, then eat until whitespace or common parse errors.
if (state._linkQuoteCharacter)
stream.eatWhile(new RegExp("[^" + state._linkQuoteCharacter + "]"));
else
stream.eatWhile(/[^\s\u00a0=<>\"\']/);
// If the stream isn't at the end of line then we found the end quote.
// In the case, change _linkTokenize to parse the end of the link next.
// Otherwise _linkTokenize will stay as-is to parse more of the link.
if (!stream.eol())
state._linkTokenize = tokenizeEndOfLinkString;
return "link";
}
function tokenizeEndOfLinkString(stream, state)
{
console.assert(state._linkQuoteCharacter !== undefined);
console.assert(state._linkBaseStyle);
// Eat the quote character to style it with the base style.
if (state._linkQuoteCharacter)
stream.eat(state._linkQuoteCharacter);
var style = state._linkBaseStyle;
// Clean up the state.
delete state._linkTokenize;
delete state._linkQuoteCharacter;
delete state._linkBaseStyle;
delete state._srcSetTokenizeState;
return style;
}
function tokenizeSrcSetString(stream, state)
{
console.assert(state._linkQuoteCharacter !== undefined);
if (state._srcSetTokenizeState === "link") {
// Eat the string until a space, comma, or ending quote.
// If this is unquoted, then eat until whitespace or common parse errors.
if (state._linkQuoteCharacter)
stream.eatWhile(new RegExp("[^\\s," + state._linkQuoteCharacter + "]"));
else
stream.eatWhile(/[^\s,\u00a0=<>\"\']/);
} else {
// Eat the string until a comma, or ending quote.
// If this is unquoted, then eat until whitespace or common parse errors.
stream.eatSpace();
if (state._linkQuoteCharacter)
stream.eatWhile(new RegExp("[^," + state._linkQuoteCharacter + "]"));
else
stream.eatWhile(/[^\s\u00a0=<>\"\']/);
stream.eatWhile(/[\s,]/);
}
// If the stream isn't at the end of line and we found the end quote
// change _linkTokenize to parse the end of the link next. Otherwise
// _linkTokenize will stay as-is to parse more of the srcset.
if (stream.eol() || (!state._linkQuoteCharacter || stream.peek() === state._linkQuoteCharacter))
state._linkTokenize = tokenizeEndOfLinkString;
// Link portion.
if (state._srcSetTokenizeState === "link") {
state._srcSetTokenizeState = "descriptor";
return "link";
}
// Descriptor portion.
state._srcSetTokenizeState = "link";
return state._linkBaseStyle;
}
function extendedXMLToken(stream, state)
{
if (state._linkTokenize) {
// Call the link tokenizer instead.
var style = state._linkTokenize(stream, state);
return style && (style + " m-" + this.name);
}
// Remember the start position so we can rewind if needed.
var startPosition = stream.pos;
var style = this._token(stream, state);
if (style === "attribute") {
// Look for "href" or "src" attributes. If found then we should
// expect a string later that should get the "link" style instead.
var text = stream.current().toLowerCase();
if (text === "src" || /\bhref\b/.test(text))
state._expectLink = true;
else if (text === "srcset")
state._expectSrcSet = true;
else {
delete state._expectLink;
delete state._expectSrcSet;
}
} else if (state._expectLink && style === "string") {
var current = stream.current();
// Unless current token is empty quotes, consume quote character
// and tokenize link next.
if (current !== "\"\"" && current !== "''") {
delete state._expectLink;
// This is a link, so setup the state to process it next.
state._linkTokenize = tokenizeLinkString;
state._linkBaseStyle = style;
// The attribute may or may not be quoted.
var quote = current[0];
state._linkQuoteCharacter = quote === "'" || quote === "\"" ? quote : null;
// Rewind the stream to the start of this token.
stream.pos = startPosition;
// Eat the open quote of the string so the string style
// will be used for the quote character.
if (state._linkQuoteCharacter)
stream.eat(state._linkQuoteCharacter);
}
} else if (state._expectSrcSet && style === "string") {
var current = stream.current();
// Unless current token is empty quotes, consume quote character
// and tokenize link next.
if (current !== "\"\"" && current !== "''") {
delete state._expectSrcSet;
// This is a link, so setup the state to process it next.
state._srcSetTokenizeState = "link";
state._linkTokenize = tokenizeSrcSetString;
state._linkBaseStyle = style;
// The attribute may or may not be quoted.
var quote = current[0];
state._linkQuoteCharacter = quote === "'" || quote === "\"" ? quote : null;
// Rewind the stream to the start of this token.
stream.pos = startPosition;
// Eat the open quote of the string so the string style
// will be used for the quote character.
if (state._linkQuoteCharacter)
stream.eat(state._linkQuoteCharacter);
}
} else if (style) {
// We don't expect other tokens between attribute and string since
// spaces and the equal character are not tokenized. So if we get
// another token before a string then we stop expecting a link.
delete state._expectLink;
delete state._expectSrcSet;
}
return style && (style + " m-" + this.name);
}
function tokenizeCSSURLString(stream, state)
{
console.assert(state._urlQuoteCharacter);
// If we are an unquoted url string, return whitespace blocks as a whitespace token (null).
if (state._unquotedURLString && stream.eatSpace())
return null;
var ch = null;
var escaped = false;
var reachedEndOfURL = false;
var lastNonWhitespace = stream.pos;
var quote = state._urlQuoteCharacter;
// Parse characters until the end of the stream/line or a proper end quote character.
while ((ch = stream.next()) != null) {
if (ch === quote && !escaped) {
reachedEndOfURL = true;
break;
}
escaped = !escaped && ch === "\\";
if (!/[\s\u00a0]/.test(ch))
lastNonWhitespace = stream.pos;
}
// If we are an unquoted url string, do not include trailing whitespace, rewind to the last real character.
if (state._unquotedURLString)
stream.pos = lastNonWhitespace;
// If we have reached the proper the end of the url string, switch to the end tokenizer to reset the state.
if (reachedEndOfURL) {
if (!state._unquotedURLString)
stream.backUp(1);
this._urlTokenize = tokenizeEndOfCSSURLString;
}
return "link";
}
function tokenizeEndOfCSSURLString(stream, state)
{
console.assert(state._urlQuoteCharacter);
console.assert(state._urlBaseStyle);
// Eat the quote character to style it with the base style.
if (!state._unquotedURLString)
stream.eat(state._urlQuoteCharacter);
var style = state._urlBaseStyle;
delete state._urlTokenize;
delete state._urlQuoteCharacter;
delete state._urlBaseStyle;
return style;
}
function extendedCSSToken(stream, state)
{
var hexColorRegex = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/g;
if (state._urlTokenize) {
// Call the link tokenizer instead.
var style = state._urlTokenize(stream, state);
return style && (style + " m-" + (this.alternateName || this.name));
}
// Remember the start position so we can rewind if needed.
var startPosition = stream.pos;
var style = this._token(stream, state);
if (style) {
if (style === "atom") {
if (stream.current() === "url") {
// If the current text is "url" then we should expect the next string token to be a link.
state._expectLink = true;
} else if (hexColorRegex.test(stream.current()))
style = style + " hex-color";
} else if (style === "error") {
if (state.state=== "atBlock" || state.state === "atBlock_parens") {
switch (stream.current()) {
case "prefers-color-scheme":
case "light":
case "dark":
case "prefers-reduced-motion":
case "reduce":
case "no-preference":
case "inverted-colors":
case "inverted":
case "color-gamut":
case "p3":
case "rec2020":
case "display-mode":
case "fullscreen":
case "standalone":
case "minimal-ui":
case "browser":
case /*-webkit-*/"video-playable-inline":
case /*-webkit-*/"transform-2d":
case /*-webkit-*/"transform-3d":
style = "property";
break;
}
}
} else if (state._expectLink) {
delete state._expectLink;
if (style === "string") {
// This is a link, so setup the state to process it next.
state._urlTokenize = tokenizeCSSURLString;
state._urlBaseStyle = style;
// The url may or may not be quoted.
var quote = stream.current()[0];
state._urlQuoteCharacter = quote === "'" || quote === "\"" ? quote : ")";
state._unquotedURLString = state._urlQuoteCharacter === ")";
// Rewind the stream to the start of this token.
stream.pos = startPosition;
// Eat the open quote of the string so the string style
// will be used for the quote character.
if (!state._unquotedURLString)
stream.eat(state._urlQuoteCharacter);
}
}
}
return style && (style + " m-" + (this.alternateName || this.name));
}
function extendedJavaScriptToken(stream, state)
{
// CodeMirror moves the original token function to _token when we extended it.
// So call it to get the style that we will add an additional class name to.
var style = this._token(stream, state);
if (style === "number" && stream.current().endsWith("n"))
style += " bigint";
return style && (style + " m-" + (this.alternateName || this.name));
}
function scrollCursorIntoView(codeMirror, event)
{
// We don't want to use the default implementation since it can cause massive jumping
// when the editor is contained inside overflow elements.
event.preventDefault();
function delayedWork()
{
// Don't try to scroll unless the editor is focused.
if (!codeMirror.getWrapperElement().classList.contains("CodeMirror-focused"))
return;
// The cursor element can contain multiple cursors. The first one is the blinky cursor,
// which is the one we want to scroll into view. It can be missing, so check first.
var cursorElement = codeMirror.getScrollerElement().getElementsByClassName("CodeMirror-cursor")[0];
if (cursorElement)
cursorElement.scrollIntoViewIfNeeded(false);
}
// We need to delay this because CodeMirror can fire scrollCursorIntoView as a view is being blurred
// and another is being focused. The blurred editor still has the focused state when this event fires.
// We don't want to scroll the blurred editor into view, only the focused editor.
setTimeout(delayedWork, 0);
}
CodeMirror.extendMode("css", {token: extendedCSSToken});
CodeMirror.extendMode("xml", {token: extendedXMLToken});
CodeMirror.extendMode("javascript", {token: extendedJavaScriptToken});
CodeMirror.defineInitHook(function(codeMirror) {
codeMirror.on("scrollCursorIntoView", scrollCursorIntoView);
});
let whitespaceStyleElement = null;
let whitespaceCountsWithStyling = new Set;
CodeMirror.defineOption("showWhitespaceCharacters", false, function(cm, value, old) {
if (!value || (old && old !== CodeMirror.Init)) {
cm.removeOverlay("whitespace");
return;
}
cm.addOverlay({
name: "whitespace",
token(stream) {
if (stream.peek() === " ") {
let count = 0;
while (stream.peek() === " ") {
++count;
stream.next();
}
if (!whitespaceCountsWithStyling.has(count)) {
whitespaceCountsWithStyling.add(count);
if (!whitespaceStyleElement)
whitespaceStyleElement = document.head.appendChild(document.createElement("style"));
const middleDot = "\\00B7";
let styleText = whitespaceStyleElement.textContent;
styleText += `.show-whitespace-characters .CodeMirror .cm-whitespace-${count}::before {`;
styleText += `content: "${middleDot.repeat(count)}";`;
styleText += `}`;
whitespaceStyleElement.textContent = styleText;
}
return `whitespace whitespace-${count}`;
}
while (!stream.eol() && stream.peek() !== " ")
stream.next();
return null;
}
});
});
CodeMirror.defineExtension("hasLineClass", function(line, where, className) {
// This matches the arguments to addLineClass and removeLineClass.
var classProperty = where === "text" ? "textClass" : (where === "background" ? "bgClass" : "wrapClass");
var lineInfo = this.lineInfo(line);
if (!lineInfo)
return false;
if (!lineInfo[classProperty])
return false;
// Test for the simple case.
if (lineInfo[classProperty] === className)
return true;
// Do a quick check for the substring. This is faster than a regex, which requires escaping the input first.
var index = lineInfo[classProperty].indexOf(className);
if (index === -1)
return false;
// Check that it is surrounded by spaces. Add padding spaces first to work with beginning and end of string cases.
var paddedClass = " " + lineInfo[classProperty] + " ";
return paddedClass.indexOf(" " + className + " ", index) !== -1;
});
CodeMirror.defineExtension("setUniqueBookmark", function(position, options) {
var marks = this.findMarksAt(position);
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__uniqueBookmark) {
marks[i].clear();
break;
}
}
var uniqueBookmark = this.setBookmark(position, options);
uniqueBookmark.__uniqueBookmark = true;
return uniqueBookmark;
});
CodeMirror.defineExtension("toggleLineClass", function(line, where, className) {
if (this.hasLineClass(line, where, className)) {
this.removeLineClass(line, where, className);
return false;
}
this.addLineClass(line, where, className);
return true;
});
CodeMirror.defineExtension("alterNumberInRange", function(amount, startPosition, endPosition, updateSelection) {
// We don't try if the range is multiline, pass to another key handler.
if (startPosition.line !== endPosition.line)
return false;
if (updateSelection) {
// Remember the cursor position/selection.
var selectionStart = this.getCursor("start");
var selectionEnd = this.getCursor("end");
}
var line = this.getLine(startPosition.line);
var foundPeriod = false;
var start = NaN;
var end = NaN;
for (var i = startPosition.ch; i >= 0; --i) {
var character = line.charAt(i);
if (character === ".") {
if (foundPeriod)
break;
foundPeriod = true;
} else if (character !== "-" && character !== "+" && isNaN(parseInt(character))) {
// Found the end already, just scan backwards.
if (i === startPosition.ch) {
end = i;
continue;
}
break;
}
start = i;
}
if (isNaN(end)) {
for (var i = startPosition.ch + 1; i < line.length; ++i) {
var character = line.charAt(i);
if (character === ".") {
if (foundPeriod) {
end = i;
break;
}
foundPeriod = true;
} else if (isNaN(parseInt(character))) {
end = i;
break;
}
end = i + 1;
}
}
// No number range found, pass to another key handler.
if (isNaN(start) || isNaN(end))
return false;
var number = parseFloat(line.substring(start, end));
// Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
// Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
var alteredNumber = Number((number + amount).toFixed(6));
var alteredNumberString = alteredNumber.toString();
var from = {line: startPosition.line, ch: start};
var to = {line: startPosition.line, ch: end};
this.replaceRange(alteredNumberString, from, to);
if (updateSelection) {
var previousLength = to.ch - from.ch;
var newLength = alteredNumberString.length;
// Fix up the selection so it follows the increase or decrease in the replacement length.
// selectionStart/End may the same object if there is no selection. If that is the case
// make only one modification to prevent a double adjustment, and keep it a single object
// to avoid CodeMirror inadvertently creating an actual selection range.
let diff = newLength - previousLength;
if (selectionStart === selectionEnd)
selectionStart.ch += diff;
else {
if (selectionStart.ch > from.ch)
selectionStart.ch += diff;
if (selectionEnd.ch > from.ch)
selectionEnd.ch += diff;
}
this.setSelection(selectionStart, selectionEnd);
}
return true;
});
function alterNumber(amount, codeMirror)
{
function findNumberToken(position)
{
// CodeMirror includes the unit in the number token, so searching for
// number tokens is the best way to get both the number and unit.
var token = codeMirror.getTokenAt(position);
if (token && token.type && /\bnumber\b/.test(token.type))
return token;
return null;
}
var position = codeMirror.getCursor("head");
var token = findNumberToken(position);
if (!token) {
// If the cursor is at the outside beginning of the token, the previous
// findNumberToken wont find it. So check the next column for a number too.
position.ch += 1;
token = findNumberToken(position);
}
if (!token)
return CodeMirror.Pass;
var foundNumber = codeMirror.alterNumberInRange(amount, {ch: token.start, line: position.line}, {ch: token.end, line: position.line}, true);
if (!foundNumber)
return CodeMirror.Pass;
}
CodeMirror.defineExtension("rectsForRange", function(range) {
var lineRects = [];
for (var line = range.start.line; line <= range.end.line; ++line) {
var lineContent = this.getLine(line);
var startChar = line === range.start.line ? range.start.ch : (lineContent.length - lineContent.trimLeft().length);
var endChar = line === range.end.line ? range.end.ch : lineContent.length;
var firstCharCoords = this.cursorCoords({ch: startChar, line});
var endCharCoords = this.cursorCoords({ch: endChar, line});
// Handle line wrapping.
if (firstCharCoords.bottom !== endCharCoords.bottom) {
var maxY = -Number.MAX_VALUE;
for (var ch = startChar; ch <= endChar; ++ch) {
var coords = this.cursorCoords({ch, line});
if (coords.bottom > maxY) {
if (ch > startChar) {
var maxX = Math.ceil(this.cursorCoords({ch: ch - 1, line}).right);
lineRects.push(new WI.Rect(minX, minY, maxX - minX, maxY - minY));
}
var minX = Math.floor(coords.left);
var minY = Math.floor(coords.top);
maxY = Math.ceil(coords.bottom);
}
}
maxX = Math.ceil(coords.right);
lineRects.push(new WI.Rect(minX, minY, maxX - minX, maxY - minY));
} else {
var minX = Math.floor(firstCharCoords.left);
var minY = Math.floor(firstCharCoords.top);
var maxX = Math.ceil(endCharCoords.right);
var maxY = Math.ceil(endCharCoords.bottom);
lineRects.push(new WI.Rect(minX, minY, maxX - minX, maxY - minY));
}
}
return lineRects;
});
let mac = WI.Platform.name === "mac";
CodeMirror.keyMap["default"] = {
"Alt-Up": alterNumber.bind(null, 1),
"Ctrl-Alt-Up": alterNumber.bind(null, 0.1),
"Shift-Alt-Up": alterNumber.bind(null, 10),
"Alt-PageUp": alterNumber.bind(null, 10),
"Shift-Alt-PageUp": alterNumber.bind(null, 100),
"Alt-Down": alterNumber.bind(null, -1),
"Ctrl-Alt-Down": alterNumber.bind(null, -0.1),
"Shift-Alt-Down": alterNumber.bind(null, -10),
"Alt-PageDown": alterNumber.bind(null, -10),
"Shift-Alt-PageDown": alterNumber.bind(null, -100),
"Cmd-/": "toggleComment",
"Cmd-D": "selectNextOccurrence",
"Shift-Tab": "indentLess",
fallthrough: mac ? "macDefault" : "pcDefault"
};
{
// CodeMirror's default behavior is to always insert a tab ("\t") regardless of `indentWithTabs`.
let original = CodeMirror.commands.insertTab;
CodeMirror.commands.insertTab = function(cm) {
if (cm.options.indentWithTabs)
original(cm);
else
CodeMirror.commands.insertSoftTab(cm);
};
}
// Register some extra MIME-types for CodeMirror. These are in addition to the
// ones CodeMirror already registers, like text/html, text/javascript, etc.
var extraXMLTypes = ["text/xml", "text/xsl"];
extraXMLTypes.forEach(function(type) {
CodeMirror.defineMIME(type, "xml");
});
var extraHTMLTypes = ["application/xhtml+xml", "image/svg+xml"];
extraHTMLTypes.forEach(function(type) {
CodeMirror.defineMIME(type, "htmlmixed");
});
var extraJavaScriptTypes = ["text/ecmascript", "application/javascript", "application/ecmascript", "application/x-javascript",
"text/x-javascript", "text/javascript1.1", "text/javascript1.2", "text/javascript1.3", "text/jscript", "text/livescript"];
extraJavaScriptTypes.forEach(function(type) {
CodeMirror.defineMIME(type, "javascript");
});
var extraJSONTypes = ["application/x-json", "text/x-json", "application/vnd.api+json"];
extraJSONTypes.forEach(function(type) {
CodeMirror.defineMIME(type, {name: "javascript", json: true});
});
// FIXME: Add WHLSL specific modes.
CodeMirror.defineMIME("x-pipeline/x-compute", CodeMirror.resolveMode("x-shader/x-vertex"));
CodeMirror.defineMIME("x-pipeline/x-render", CodeMirror.resolveMode("x-shader/x-vertex"));
})();
WI.compareCodeMirrorPositions = function(a, b)
{
var lineCompare = a.line - b.line;
if (lineCompare !== 0)
return lineCompare;
var aColumn = "ch" in a ? a.ch : Number.MAX_VALUE;
var bColumn = "ch" in b ? b.ch : Number.MAX_VALUE;
return aColumn - bColumn;
};
WI.walkTokens = function(cm, mode, initialPosition, callback)
{
let state = CodeMirror.copyState(mode, cm.getTokenAt(initialPosition).state);
if (state.localState)
state = state.localState;
let lineCount = cm.lineCount();
let abort = false;
for (let lineNumber = initialPosition.line; !abort && lineNumber < lineCount; ++lineNumber) {
let line = cm.getLine(lineNumber);
let stream = new CodeMirror.StringStream(line);
if (lineNumber === initialPosition.line)
stream.start = stream.pos = initialPosition.ch;
while (!stream.eol()) {
let tokenType = mode.token(stream, state);
if (!callback(tokenType, stream.current())) {
abort = true;
break;
}
stream.start = stream.pos;
}
}
if (!abort)
callback(null);
};
WI.tokenizeCSSValue = function(cssValue)
{
const rulePrefix = "*{X:";
let cssRule = rulePrefix + cssValue + "}";
let tokens = [];
let mode = CodeMirror.getMode({indentUnit: 0}, "text/css");
let state = CodeMirror.startState(mode);
let stream = new CodeMirror.StringStream(cssRule);
function processToken(token, tokenType, column) {
if (column < rulePrefix.length)
return;
if (token === "}" && !tokenType)
return;
tokens.push({value: token, type: tokenType});
}
while (!stream.eol()) {
let style = mode.token(stream, state);
let value = stream.current();
processToken(value, style, stream.start);
stream.start = stream.pos;
}
return tokens;
};
+74
View File
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2015-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.CodeMirrorEditor = class CodeMirrorEditor
{
static create(element, options)
{
// CodeMirror's manual scrollbar positioning results in double scrollbars,
// nor does it handle braces and brackets well, so default to using LTR.
// Clients can override this if custom layout for RTL is available.
element.setAttribute("dir", "ltr");
element.classList.toggle("read-only", options.readOnly);
let codeMirror = new CodeMirror(element, {
// These values will be overridden by any value with the same key in `options`.
indentWithTabs: WI.settings.indentWithTabs.value,
indentUnit: WI.settings.indentUnit.value,
tabSize: WI.settings.tabSize.value,
lineWrapping: WI.settings.enableLineWrapping.value,
showWhitespaceCharacters: WI.settings.showWhitespaceCharacters.value,
...options,
});
function listenForChange(setting, codeMirrorOption) {
if (options[codeMirrorOption] !== undefined)
return;
setting.addEventListener(WI.Setting.Event.Changed, function(event) {
this.setOption(codeMirrorOption, setting.value);
}, codeMirror);
}
listenForChange(WI.settings.indentWithTabs, "indentWithTabs");
listenForChange(WI.settings.indentUnit, "indentUnit");
listenForChange(WI.settings.tabSize, "tabSize");
listenForChange(WI.settings.enableLineWrapping, "lineWrapping");
listenForChange(WI.settings.showWhitespaceCharacters, "showWhitespaceCharacters");
// Override some Mac specific keybindings.
if (WI.Platform.name === "mac") {
codeMirror.addKeyMap({
"Home": () => { codeMirror.scrollIntoView({line: 0, ch: 0}); },
"End": () => { codeMirror.scrollIntoView({line: codeMirror.lineCount() - 1, ch: null}); },
});
}
// Set up default controllers that should be present for
// all CodeMirror editor instances.
new WI.CodeMirrorTextKillController(codeMirror);
return codeMirror;
}
};
+449
View File
@@ -0,0 +1,449 @@
/*
* 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.
*/
// In the inspector token types have been modified to include extra mode information
// after the actual token type. So we can't do token === "foo". So instead we do
// /\bfoo\b/.test(token).
CodeMirror.extendMode("javascript", {
shouldHaveSpaceBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (!token) {
if (content === "(") // Most keywords like "if (" but not "function(" or "typeof(".
return lastToken && /\bkeyword\b/.test(lastToken) && (lastContent !== "function" && lastContent !== "typeof" && lastContent !== "instanceof");
if (content === ":") // Ternary.
return state.lexical.type === "stat" || state.lexical.type === ")" || state.lexical.type === "]";
return false;
}
if (isComment)
return true;
if (/\boperator\b/.test(token)) {
if (!lastToken && (content === "+" || content === "-" || content === "~") && (lastContent !== ")" && lastContent !== "]")) // Possible Unary +/-.
return false;
if (content === "!") // Unary ! should not be confused with "!=".
return false;
return "+-/*%&&||!===+=-=>=<=?".indexOf(content) >= 0; // Operators.
}
if (/\bkeyword\b/.test(token)) { // Most keywords require spaces before them, unless a '}' can come before it.
if (content === "else" || content === "catch" || content === "finally")
return lastContent === "}";
if (content === "while" && lastContent === "}")
return state._jsPrettyPrint.lastContentBeforeBlock === "do";
return false;
}
return false;
},
shouldHaveSpaceAfterLastToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (lastToken && /\bkeyword\b/.test(lastToken)) { // Most keywords require spaces after them, unless a '{' or ';' can come after it.
if (lastContent === "else")
return true;
if (lastContent === "catch")
return true;
if (lastContent === "return")
return content !== ";";
if (lastContent === "throw")
return true;
if (lastContent === "try")
return true;
if (lastContent === "finally")
return true;
if (lastContent === "do")
return true;
return false;
}
if (lastToken && /\bcomment\b/.test(lastToken)) // Embedded /* comment */.
return true;
if (lastContent === ")") // "){".
return content === "{";
if (lastContent === ";") // In for loop.
return state.lexical.type === ")";
if (lastContent === "!") // Unary ! should not be confused with "!=".
return false;
// If this unary operator did not have a leading expression it is probably unary.
if ((lastContent === "+" || lastContent === "-" || lastContent === "~") && !state._jsPrettyPrint.unaryOperatorHadLeadingExpr)
return false;
return ",+-/*%&&||:!===+=-=>=<=?".indexOf(lastContent) >= 0; // Operators.
},
newlinesAfterToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (!token) {
if (content === ",") // In object literals, like in {a:1,b:2}, but not in param lists or vardef lists.
return state.lexical.type === "}" ? 1 : 0;
if (content === ";") // Everywhere except in for loop conditions.
return state.lexical.type !== ")" ? 1 : 0;
if (content === ":" && state.lexical.type === "}" && state.lexical.prev && state.lexical.prev.type === "form") // Switch case/default.
return 1;
return content.length === 1 && "{}".indexOf(content) >= 0 ? 1 : 0; // After braces.
}
if (isComment)
return 1;
return 0;
},
removeLastWhitespace: function(lastToken, lastContent, token, state, content, isComment)
{
return false;
},
removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
{
if (!token) {
if (content === "}") // "{}".
return lastContent === "{";
if (content === ";") // "x = {};" or ";;".
return "};".indexOf(lastContent) >= 0;
if (content === ":") // Ternary.
return lastContent === "}" && (state.lexical.type === "stat" || state.lexical.type === ")" || state.lexical.type === "]");
if (",().".indexOf(content) >= 0) // "})", "}.bind", "function() { ... }()", or "}, false)".
return lastContent === "}";
return false;
}
if (isComment) { // Comment after semicolon.
if (!firstTokenOnLine && lastContent === ";")
return true;
return false;
}
if (/\bkeyword\b/.test(token)) {
if (content === "else" || content === "catch" || content === "finally") // "} else", "} catch", "} finally"
return lastContent === "}";
if (content === "while" && lastContent === "}")
return state._jsPrettyPrint.lastContentBeforeBlock === "do";
return false;
}
return false;
},
indentAfterToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (content === "{")
return true;
if (content === "case" || content === "default")
return state.lexical.type === "}" && state.lexical.prev && state.lexical.prev.type === "form"; // Switch case/default.
return false;
},
newlineBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (state._jsPrettyPrint.shouldIndent)
return true;
return content === "}" && lastContent !== "{"; // "{}"
},
indentBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (state._jsPrettyPrint.shouldIndent)
return true;
return false;
},
dedentsBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
var dedent = 0;
if (state._jsPrettyPrint.shouldDedent)
dedent += state._jsPrettyPrint.dedentSize;
if (!token && content === "}")
dedent += 1;
else if (token && /\bkeyword\b/.test(token) && (content === "case" || content === "default"))
dedent += 1;
return dedent;
},
modifyStateForTokenPre: function(lastToken, lastContent, token, state, content, isComment)
{
if (!state._jsPrettyPrint) {
state._jsPrettyPrint = {
indentCount: 0, // How far have we indented because of single statement blocks.
shouldIndent: false, // Signal we should indent on entering a single statement block.
shouldDedent: false, // Signal we should dedent on leaving a single statement block.
dedentSize: 0, // How far we should dedent when leaving a single statement block.
lastIfIndentCount: 0, // Keep track of the indent the last time we saw an if without braces.
openBraceStartMarkers: [], // Keep track of non-single statement blocks.
openBraceTrackingCount: -1, // Keep track of "{" and "}" in non-single statement blocks.
unaryOperatorHadLeadingExpr: false, // Try to detect if a unary operator had a leading expression and therefore may be binary.
lastContentBeforeBlock: undefined, // Used to detect if this was a do/while.
};
}
// - Entering:
// - Preconditions:
// - last lexical was a "form" we haven't encountered before
// - last content was ")", "else", or "do"
// - current lexical is not ")" (in an expression or condition)
// - Cases:
// 1. "{"
// - indent +0
// - save this indent size so when we encounter the "}" we know how far to dedent
// 2. "else if"
// - indent +0 and do not signal to add a newline and indent
// - mark the last if location so when we encounter an "else" we know how far to dedent
// - mark the lexical state so we know we are inside a single statement block
// 3. Token without brace.
// - indent +1 and signal to add a newline and indent
// - mark the last if location so when we encounter an "else" we know how far to dedent
// - mark the lexical state so we know we are inside a single statement block
if (!isComment && state.lexical.prev && state.lexical.prev.type === "form" && !state.lexical.prev._jsPrettyPrintMarker && (lastContent === ")" || lastContent === "else" || lastContent === "do") && (state.lexical.type !== ")")) {
if (content === "{") {
// Save the state at the opening brace so we can return to it when we see "}".
var savedState = {indentCount: state._jsPrettyPrint.indentCount, openBraceTrackingCount: state._jsPrettyPrint.openBraceTrackingCount, lastContentBeforeBlock: lastContent};
state._jsPrettyPrint.openBraceStartMarkers.push(savedState);
state._jsPrettyPrint.openBraceTrackingCount = 1;
} else if (state.lexical.type !== "}") {
// Increase the indent count. Signal for a newline and indent if needed.
if (!(lastContent === "else" && content === "if")) {
state._jsPrettyPrint.indentCount++;
state._jsPrettyPrint.shouldIndent = true;
}
state.lexical.prev._jsPrettyPrintMarker = true;
if (state._jsPrettyPrint.enteringIf)
state._jsPrettyPrint.lastIfIndentCount = state._jsPrettyPrint.indentCount - 1;
}
}
// - Leaving:
// - Preconditions:
// - ignore ";", wait for the next token instead.
// - Cases:
// 1. "else"
// - dedent to the last "if"
// 2. "}" and all braces we saw are balanced
// - dedent to the last "{"
// 3. Token without a marker on the stack
// - dedent all the way
else {
console.assert(!state._jsPrettyPrint.shouldDedent);
console.assert(!state._jsPrettyPrint.dedentSize);
// Track "{" and "}" to know when the "}" is really closing a block.
if (!isComment) {
if (content === "{")
state._jsPrettyPrint.openBraceTrackingCount++;
else if (content === "}")
state._jsPrettyPrint.openBraceTrackingCount--;
}
if (content === ";") {
// Ignore.
} else if (content === "else") {
// Dedent to the last "if".
if (lastContent !== "}") {
state._jsPrettyPrint.shouldDedent = true;
state._jsPrettyPrint.dedentSize = state._jsPrettyPrint.indentCount - state._jsPrettyPrint.lastIfIndentCount;
state._jsPrettyPrint.lastIfIndentCount = 0;
}
} else if (content === "}" && !state._jsPrettyPrint.openBraceTrackingCount && state._jsPrettyPrint.openBraceStartMarkers.length) {
// Dedent to the last "{".
var savedState = state._jsPrettyPrint.openBraceStartMarkers.pop();
state._jsPrettyPrint.shouldDedent = true;
state._jsPrettyPrint.dedentSize = state._jsPrettyPrint.indentCount - savedState.indentCount;
state._jsPrettyPrint.openBraceTrackingCount = savedState.openBraceTrackingCount;
state._jsPrettyPrint.lastContentBeforeBlock = savedState.lastContentBeforeBlock;
} else {
// Dedent all the way.
var shouldDedent = true;
var lexical = state.lexical.prev;
while (lexical) {
if (lexical._jsPrettyPrintMarker) {
shouldDedent = false;
break;
}
lexical = lexical.prev;
}
if (shouldDedent) {
state._jsPrettyPrint.shouldDedent = true;
state._jsPrettyPrint.dedentSize = state._jsPrettyPrint.indentCount;
}
}
}
// Signal for when we will be entering an if.
if (token && state.lexical.type === "form" && state.lexical.prev && state.lexical.prev !== "form" && /\bkeyword\b/.test(token))
state._jsPrettyPrint.enteringIf = content === "if";
},
modifyStateForTokenPost: function(lastToken, lastContent, token, state, content, isComment)
{
if (state._jsPrettyPrint.shouldIndent)
state._jsPrettyPrint.shouldIndent = false;
if (state._jsPrettyPrint.shouldDedent) {
state._jsPrettyPrint.indentCount -= state._jsPrettyPrint.dedentSize;
state._jsPrettyPrint.dedentSize = 0;
state._jsPrettyPrint.shouldDedent = false;
}
if ((content === "+" || content === "-" || content === "~") && (lastContent === ")" || lastContent === "]" || /\b(?:variable|number)\b/.test(lastToken)))
state._jsPrettyPrint.unaryOperatorHadLeadingExpr = true;
else
state._jsPrettyPrint.unaryOperatorHadLeadingExpr = false;
}
});
CodeMirror.extendMode("css", {
shouldHaveSpaceBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (!token) {
if (content === "{")
return true;
return ">+~-*/".indexOf(content) >= 0; // calc() expression or child/sibling selectors
}
if (isComment)
return true;
if (/\bkeyword\b/.test(token)) {
if (content.charAt(0) === "!") // "!important".
return true;
return false;
}
return false;
},
shouldHaveSpaceAfterLastToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (!lastToken) {
if (lastContent === ",")
return true;
if (lastContent === ":") // Space in "prop: value" but not in a selectors "a:link" or "div::after" or media queries "(max-device-width:480px)".
return state.state === "prop";
if (lastContent === ")" && (content !== ")" && content !== ",")) {
if (/\bnumber\b/.test(token)) // linear-gradient(rgb(...)0%,rgb(...)100%)
return true;
if (state.state === "prop") // -webkit-transform:rotate(...)translate(...);
return true;
if (state.state === "media" || state.state === "atBlock_parens") // Space in "not(foo)and" but not at the end of "not(not(foo))"
return true;
return false; // color: rgb(...);
}
return ">+~-*/".indexOf(lastContent) >= 0; // calc() expression or child/sibling selectors
}
if (/\bcomment\b/.test(lastToken))
return true;
if (/\bkeyword\b/.test(lastToken)) // media-query keywords
return state.state === "media" || (state.state === "atBlock_parens" && content !== ")");
return false;
},
newlinesAfterToken: function(lastToken, lastContent, token, state, content, isComment)
{
if (!token) {
if (content === ";")
return 1;
if (content === ",") { // "a,b,c,...,z{}" rule list at top level or in @media top level and only if the line length will be large.
if ((state.state === "top" || state.state === "media") && state._cssPrettyPrint.lineLength > 60) {
state._cssPrettyPrint.lineLength = 0;
return 1;
}
return 0;
}
if (content === "{")
return 1;
if (content === "}") // 2 newlines between rule declarations.
return 2;
return 0;
}
if (isComment)
return 1;
return 0;
},
removeLastWhitespace: function(lastToken, lastContent, token, state, content, isComment)
{
return false;
},
removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
{
if (isComment) { // Comment after semicolon.
if (!firstTokenOnLine && lastContent === ";")
return true;
return false;
}
return content === "}" && (lastContent === "{" || lastContent === "}"); // "{}" and "}\n}" when closing @media.
},
indentAfterToken: function(lastToken, lastContent, token, state, content, isComment)
{
return content === "{";
},
newlineBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
return content === "}" && (lastContent !== "{" && lastContent !== "}"); // "{}" and "}\n}" when closing @media.
},
indentBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
return false;
},
dedentsBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
{
return content === "}" ? 1 : 0;
},
modifyStateForTokenPost: function(lastToken, lastContent, token, state, content, isComment)
{
if (!state._cssPrettyPrint)
state._cssPrettyPrint = {lineLength: 0};
// In order insert newlines in selector lists we need keep track of the length of the current line.
// This isn't exact line length, only the builder knows that, but it is good enough to get an idea.
// If we are at a top level, keep track of the current line length, otherwise we reset to 0.
if (!isComment && (state.state === "top" || state.state === "media" || state.state === "pseudo"))
state._cssPrettyPrint.lineLength += content.length;
else
state._cssPrettyPrint.lineLength = 0;
}
});
@@ -0,0 +1,32 @@
/*
* 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.
*/
.cm-s-default .cm-local-override-url-bad-scheme {
color: red;
}
.cm-s-default .cm-local-override-url-fragment {
color: red;
}
@@ -0,0 +1,91 @@
/*
* 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.
*/
CodeMirror.defineMode("local-override-url", function() {
function tokenBase(stream, state) {
// Parse only the first line, ignore all other lines.
if (state.parsedURL && stream.sol()) {
stream.skipToEnd();
return null;
}
// Parse once per tokenize.
let url = stream.string;
if (!state.parsedURL) {
try {
state.parsedURL = new URL(url);
let indexOfFragment = -1;
if (state.parsedURL.hash)
indexOfFragment = url.lastIndexOf(state.parsedURL.hash);
else if (url.endsWith("#"))
indexOfFragment = url.length - 1;
state.indexOfFragment = indexOfFragment;
} catch {
stream.skipToEnd();
return null;
}
}
// Scheme
if (stream.pos < state.parsedURL.protocol.length) {
if (state.parsedURL.protocol !== "http:" && state.parsedURL.protocol !== "https:") {
while (stream.pos < state.parsedURL.protocol.length && !stream.eol())
stream.next();
return "local-override-url-bad-scheme";
}
}
// Pre-fragment string.
let indexOfFragment = state.indexOfFragment;
if (indexOfFragment === -1) {
stream.skipToEnd();
return null;
}
if (stream.pos < indexOfFragment) {
while (stream.pos < indexOfFragment && !stream.eol())
stream.next();
return null;
}
// Fragment.
stream.skipToEnd();
return "local-override-url-fragment";
}
return {
startState: function() {
return {
parsedURL: null,
tokenize: tokenBase,
};
},
token: function(stream, state) {
return state.tokenize(stream, state);
}
};
});
CodeMirror.defineMIME("text/x-local-override-url", "local-override-url");
+139
View File
@@ -0,0 +1,139 @@
/*
* 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 {
z-index: 0;
}
.CodeMirror,
.CodeMirror * {
box-sizing: content-box;
}
.CodeMirror .CodeMirror-lines {
/* One pixel bottom padding needed to show the bottom border for matching brackets and search matches. */
padding: 0 0 1px 0;
}
.CodeMirror pre {
padding: 0 3px 0 7px;
}
.CodeMirror .CodeMirror-selected {
background-color: var(--selected-background-color-unfocused);
}
.CodeMirror.CodeMirror-focused .CodeMirror-selected {
background-color: highlight;
}
.CodeMirror .CodeMirror-cursor {
pointer-events: none;
}
.CodeMirror .CodeMirror-lines .CodeMirror-matchingbracket {
color: inherit;
background-color: hsla(226, 77%, 65%, 0.2);
border-bottom: 1px dotted hsl(240, 84%, 50%);
}
.CodeMirror .CodeMirror-lines .CodeMirror-nonmatchingbracket {
color: inherit;
background-color: hsla(2, 84%, 50%, 0.2);
border-bottom: 1px dotted hsl(2, 84%, 50%);
}
.CodeMirror .CodeMirror-gutters {
background-color: hsl(0, 0%, 96%);
border-right: 1px solid hsl(0, 0%, 90%);
}
.CodeMirror .CodeMirror-linenumber {
padding: 0 2px;
min-width: 22px;
color: hsl(0, 0%, 57%);
font: 8px/13px -webkit-system-font, Menlo, Monaco, monospace;
font-variant-numeric: tabular-nums;
text-align: right;
}
.CodeMirror-linewidget {
-webkit-user-select: text;
}
.CodeMirror .CodeMirror-placeholder {
font-family: -webkit-system-font, sans-serif;
color: hsl(0, 0%, 65%);
text-indent: 0;
}
.CodeMirror .cm-tab {
position: relative;
}
.CodeMirror .cm-invalidchar {
display: none;
}
.show-whitespace-characters .CodeMirror .cm-tab::before {
position: absolute;
width: 90%;
bottom: 50%;
left: 5%;
content: "";
border-bottom: 1px solid hsl(0, 0%, 70%);
}
.show-whitespace-characters .CodeMirror .cm-whitespace::before {
position: absolute;
pointer-events: none;
color: hsl(0, 0%, 70%);
}
.show-invisible-characters .CodeMirror .cm-invalidchar {
display: initial;
}
@media (prefers-color-scheme: dark) {
.CodeMirror-cursor {
border-left-color: hsl(0, 0%, var(--foreground-lightness));
}
.CodeMirror .CodeMirror-gutters {
background-color: var(--background-color);
border-right-color: var(--text-color-quaternary);
}
.cm-s-default .cm-link {
color: var(--syntax-highlight-link-color);
}
.CodeMirror .CodeMirror-lines .CodeMirror-matchingbracket {
background-color: hsla(232, 100%, 72%, 0.4);
border-bottom-color: hsl(206, 100%, 65%);
}
}
+48
View File
@@ -0,0 +1,48 @@
/*
* 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.
*/
.cm-s-default .cm-regex-character-set {
color: var(--syntax-highlight-string-color);
}
.cm-s-default .cm-regex-character-set-negate {
color: var(--syntax-highlight-number-color);
}
.cm-s-default :matches(.cm-regex-escape, .cm-regex-escape-2, .cm-regex-escape-3) {
color: var(--syntax-highlight-symbol-color);
}
.cm-s-default :matches(.cm-regex-group, .cm-regex-lookahead) {
color: var(--syntax-highlight-regex-group-color);
}
.cm-s-default .cm-regex-quantifier {
color: var(--syntax-highlight-number-color);
}
.cm-s-default :matches(.cm-regex-literal, .cm-regex-special, .cm-regex-backreference) {
color: var(--syntax-highlight-boolean-color);
}

Some files were not shown because too many files have changed in this diff Show More