blob: 02b76f18b75788bf1eea7ffc9a2150cd8e519564 [file] [log] [blame]
/*
* 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.OverlayManager = class OverlayManager extends WI.Object
{
constructor()
{
super();
this._gridOverlayForNodeMap = new Map;
this._nextDefaultGridColorIndex = 0;
this._gridColorForNodeMap = new WeakMap;
this._gridColorSettingForNodeMap = new WeakMap;
WI.settings.gridOverlayShowExtendedGridLines.addEventListener(WI.Setting.Event.Changed, this._handleGridSettingChanged, this);
WI.settings.gridOverlayShowLineNames.addEventListener(WI.Setting.Event.Changed, this._handleGridSettingChanged, this);
WI.settings.gridOverlayShowLineNumbers.addEventListener(WI.Setting.Event.Changed, this._handleGridSettingChanged, this);
WI.settings.gridOverlayShowTrackSizes.addEventListener(WI.Setting.Event.Changed, this._handleGridSettingChanged, this);
WI.settings.gridOverlayShowAreaNames.addEventListener(WI.Setting.Event.Changed, this._handleGridSettingChanged, this);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
}
// Public
showGridOverlay(domNode, {color, initiator} = {})
{
console.assert(!domNode.destroyed, domNode);
if (domNode.destroyed)
return;
console.assert(domNode instanceof WI.DOMNode, domNode);
console.assert(!color || color instanceof WI.Color, color);
console.assert(domNode.layoutContextType === WI.DOMNode.LayoutContextType.Grid, domNode.layoutContextType);
color ||= this.getGridColorForNode(domNode);
let target = WI.assumingMainTarget();
let commandArguments = {
nodeId: domNode.id,
gridColor: color.toProtocol(),
showLineNames: WI.settings.gridOverlayShowLineNames.value,
showLineNumbers: WI.settings.gridOverlayShowLineNumbers.value,
showExtendedGridLines: WI.settings.gridOverlayShowExtendedGridLines.value,
showTrackSizes: WI.settings.gridOverlayShowTrackSizes.value,
showAreaNames: WI.settings.gridOverlayShowAreaNames.value,
};
target.DOMAgent.showGridOverlay.invoke(commandArguments);
let overlay = {domNode, ...commandArguments, initiator};
// The method to show the overlay will be called repeatedly while updating the grid overlay color. Avoid adding duplicate event listeners
if (!this._gridOverlayForNodeMap.has(domNode))
domNode.addEventListener(WI.DOMNode.Event.LayoutContextTypeChanged, this._handleLayoutContextTypeChanged, this);
this._gridOverlayForNodeMap.set(domNode, overlay);
this.dispatchEventToListeners(WI.OverlayManager.Event.GridOverlayShown, overlay);
}
hideGridOverlay(domNode)
{
console.assert(domNode instanceof WI.DOMNode, domNode);
console.assert(!domNode.destroyed, domNode);
console.assert(domNode.layoutContextType === WI.DOMNode.LayoutContextType.Grid, domNode.layoutContextType);
if (domNode.destroyed)
return;
let overlay = this._gridOverlayForNodeMap.take(domNode);
if (!overlay)
return;
let target = WI.assumingMainTarget();
target.DOMAgent.hideGridOverlay(domNode.id);
domNode.removeEventListener(WI.DOMNode.Event.LayoutContextTypeChanged, this._handleLayoutContextTypeChanged, this);
this.dispatchEventToListeners(WI.OverlayManager.Event.GridOverlayHidden, overlay);
}
hasVisibleGridOverlays()
{
return this._gridOverlayForNodeMap.size > 0;
}
isGridOverlayVisible(domNode)
{
return this._gridOverlayForNodeMap.has(domNode);
}
toggleGridOverlay(domNode, options)
{
if (this.isGridOverlayVisible(domNode))
this.hideGridOverlay(domNode);
else
this.showGridOverlay(domNode, options);
}
getGridColorForNode(domNode)
{
let color = this._gridColorForNodeMap.get(domNode);
if (color)
return color;
const defaultGridHSLColors = [
[329, 91, 70],
[207, 96, 69],
[92, 90, 64],
[291, 73, 68],
[40, 97, 57],
];
let colorSetting = this._gridColorSettingForNodeMap.get(domNode);
if (!colorSetting) {
let defaultColor = defaultGridHSLColors[this._nextDefaultGridColorIndex];
this._nextDefaultGridColorIndex = (this._nextDefaultGridColorIndex + 1) % defaultGridHSLColors.length;
let url = domNode.ownerDocument.documentURL || WI.networkManager.mainFrame.url;
colorSetting = new WI.Setting(`grid-overlay-color-${url.hash}-${domNode.path().hash}`, defaultColor);
this._gridColorSettingForNodeMap.set(domNode, colorSetting);
}
color = new WI.Color(WI.Color.Format.HSL, colorSetting.value);
this._gridColorForNodeMap.set(domNode, color);
return color;
}
setGridColorForNode(domNode, color)
{
console.assert(domNode instanceof WI.DOMNode, domNode);
console.assert(color instanceof WI.Color, color);
let colorSetting = this._gridColorSettingForNodeMap.get(domNode);
console.assert(colorSetting, "There should already be a setting created form a previous call to getGridColorForNode()");
colorSetting.value = color.hsl;
this._gridColorForNodeMap.set(domNode, color);
}
// Private
_handleLayoutContextTypeChanged(event)
{
let domNode = event.target;
console.assert(domNode.layoutContextType !== WI.DOMNode.LayoutContextType.Grid, domNode);
domNode.removeEventListener(WI.DOMNode.Event.LayoutContextTypeChanged, this._handleLayoutContextTypeChanged, this);
// When the context type changes, the overlay is automatically hidden on the backend. Here, we only update the map and notify listeners.
let overlay = this._gridOverlayForNodeMap.take(domNode);
this.dispatchEventToListeners(WI.OverlayManager.Event.GridOverlayHidden, overlay);
}
_handleGridSettingChanged(event)
{
for (let [domNode, overlay] of this._gridOverlayForNodeMap) {
// Refresh all shown overlays. Latest settings values will be used.
this.showGridOverlay(domNode, {color: overlay.color, initiator: overlay.initiator});
}
}
_handleMainResourceDidChange(event)
{
// Consider the following scenario:
// 1. Click on the badge of an element with `display: grid`. The 1st overlay color is used.
// 2. Reload the webpage.
// 3. Click on the badge of the same element.
//
// We should see the same 1st default overlay color. If we don't reset _nextDefaultGridColorIndex,
// the 2nd default color would be used instead.
//
// `domNode.id` is different for the same DOM element after page reload.
this._nextDefaultGridColorIndex = 0;
}
};
WI.OverlayManager.Event = {
GridOverlayShown: "overlay-manager-grid-overlay-shown",
GridOverlayHidden: "overlay-manager-grid-overlay-hidden",
};