summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/Popover.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/Popover.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/Popover.js603
1 files changed, 603 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/Popover.js b/Source/WebInspectorUI/UserInterface/Views/Popover.js
new file mode 100644
index 000000000..3edee8c85
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/Popover.js
@@ -0,0 +1,603 @@
+/*
+ * 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.
+ */
+
+WebInspector.Popover = class Popover extends WebInspector.Object
+{
+ constructor(delegate)
+ {
+ super();
+
+ this.delegate = delegate;
+ this._edge = null;
+ this._frame = new WebInspector.Rect;
+ this._content = null;
+ this._targetFrame = new WebInspector.Rect;
+ this._anchorPoint = new WebInspector.Point;
+ this._preferredEdges = null;
+ this._resizeHandler = null;
+
+ this._contentNeedsUpdate = false;
+ this._dismissing = false;
+
+ this._canvasId = "popover-" + (WebInspector.Popover.canvasId++);
+
+ this._element = document.createElement("div");
+ this._element.className = "popover";
+ this._element.style.backgroundImage = "-webkit-canvas(" + this._canvasId + ")";
+ this._element.addEventListener("transitionend", this, true);
+
+ this._container = this._element.appendChild(document.createElement("div"));
+ this._container.className = "container";
+ }
+
+ // Public
+
+ get element()
+ {
+ return this._element;
+ }
+
+ get frame()
+ {
+ return this._frame;
+ }
+
+ get visible()
+ {
+ return this._element.parentNode === document.body && !this._element.classList.contains(WebInspector.Popover.FadeOutClassName);
+ }
+
+ set frame(frame)
+ {
+ this._element.style.left = frame.minX() + "px";
+ this._element.style.top = frame.minY() + "px";
+ this._element.style.width = frame.size.width + "px";
+ this._element.style.height = frame.size.height + "px";
+ this._element.style.backgroundSize = frame.size.width + "px " + frame.size.height + "px";
+ this._frame = frame;
+ }
+
+ set content(content)
+ {
+ if (content === this._content)
+ return;
+
+ this._content = content;
+
+ this._contentNeedsUpdate = true;
+
+ if (this.visible)
+ this._update(true);
+ }
+
+ set windowResizeHandler(resizeHandler)
+ {
+ console.assert(typeof resizeHandler === "function");
+ this._resizeHandler = resizeHandler;
+ }
+
+ update(shouldAnimate = true)
+ {
+ if (!this.visible)
+ return;
+
+ var previouslyFocusedElement = document.activeElement;
+
+ this._contentNeedsUpdate = true;
+ this._update(shouldAnimate);
+
+ if (previouslyFocusedElement)
+ previouslyFocusedElement.focus();
+ }
+
+ present(targetFrame, preferredEdges)
+ {
+ this._targetFrame = targetFrame;
+ this._preferredEdges = preferredEdges;
+
+ if (!this._content)
+ return;
+
+ this._addListenersIfNeeded();
+
+ this._update();
+ }
+
+ presentNewContentWithFrame(content, targetFrame, preferredEdges)
+ {
+ this._content = content;
+ this._contentNeedsUpdate = true;
+
+ this._targetFrame = targetFrame;
+ this._preferredEdges = preferredEdges;
+
+ this._addListenersIfNeeded();
+
+ var shouldAnimate = this.visible;
+ this._update(shouldAnimate);
+ }
+
+ dismiss()
+ {
+ if (this._dismissing || this._element.parentNode !== document.body)
+ return;
+
+ this._dismissing = true;
+
+ console.assert(this._isListeningForPopoverEvents);
+ this._isListeningForPopoverEvents = false;
+
+ window.removeEventListener("mousedown", this, true);
+ window.removeEventListener("scroll", this, true);
+ window.removeEventListener("resize", this, true);
+ window.removeEventListener("keypress", this, true);
+
+ WebInspector.quickConsole.keyboardShortcutDisabled = false;
+
+ this._element.classList.add(WebInspector.Popover.FadeOutClassName);
+
+ if (this.delegate && typeof this.delegate.willDismissPopover === "function")
+ this.delegate.willDismissPopover(this);
+ }
+
+ handleEvent(event)
+ {
+ switch (event.type) {
+ case "mousedown":
+ case "scroll":
+ if (!this._element.contains(event.target) && !event.target.enclosingNodeOrSelfWithClass(WebInspector.Popover.IgnoreAutoDismissClassName)
+ && !event[WebInspector.Popover.EventPreventDismissSymbol]) {
+ this.dismiss();
+ }
+ break;
+ case "resize":
+ if (this._resizeHandler)
+ this._resizeHandler();
+ break;
+ case "keypress":
+ if (event.keyCode === WebInspector.KeyboardShortcut.Key.Escape.keyCode)
+ this.dismiss();
+ break;
+ case "transitionend":
+ if (event.target === this._element) {
+ document.body.removeChild(this._element);
+ this._element.classList.remove(WebInspector.Popover.FadeOutClassName);
+ this._container.textContent = "";
+ if (this.delegate && typeof this.delegate.didDismissPopover === "function")
+ this.delegate.didDismissPopover(this);
+
+ this._dismissing = false;
+ break;
+ }
+ break;
+ }
+ }
+
+ // Private
+
+ _update(shouldAnimate)
+ {
+ if (shouldAnimate)
+ var previousEdge = this._edge;
+
+ var targetFrame = this._targetFrame;
+ var preferredEdges = this._preferredEdges;
+
+ // Ensure our element is on display so that its metrics can be resolved
+ // or interrupt any pending transition to remove it from display.
+ if (this._element.parentNode !== document.body)
+ document.body.appendChild(this._element);
+ else
+ this._element.classList.remove(WebInspector.Popover.FadeOutClassName);
+
+ this._dismissing = false;
+
+ if (this._contentNeedsUpdate) {
+ // Reset CSS properties on element so that the element may be sized to fit its content.
+ this._element.style.removeProperty("left");
+ this._element.style.removeProperty("top");
+ this._element.style.removeProperty("width");
+ this._element.style.removeProperty("height");
+ if (this._edge !== null)
+ this._element.classList.remove(this._cssClassNameForEdge());
+
+ // Add the content in place of the wrapper to get the raw metrics.
+ this._container.replaceWith(this._content);
+
+ // Get the ideal size for the popover to fit its content.
+ var popoverBounds = this._element.getBoundingClientRect();
+ this._preferredSize = new WebInspector.Size(Math.ceil(popoverBounds.width), Math.ceil(popoverBounds.height));
+ }
+
+ var titleBarOffset = WebInspector.Platform.name === "mac" && WebInspector.Platform.version.release >= 10 ? 22 : 0;
+ var containerFrame = new WebInspector.Rect(0, titleBarOffset, window.innerWidth, window.innerHeight - titleBarOffset);
+ // The frame of the window with a little inset to make sure we have room for shadows.
+ containerFrame = containerFrame.inset(WebInspector.Popover.ShadowEdgeInsets);
+
+ // Work out the metrics for all edges.
+ var metrics = new Array(preferredEdges.length);
+ for (var edgeName in WebInspector.RectEdge) {
+ var edge = WebInspector.RectEdge[edgeName];
+ var item = {
+ edge,
+ metrics: this._bestMetricsForEdge(this._preferredSize, targetFrame, containerFrame, edge)
+ };
+ var preferredIndex = preferredEdges.indexOf(edge);
+ if (preferredIndex !== -1)
+ metrics[preferredIndex] = item;
+ else
+ metrics.push(item);
+ }
+
+ function area(size)
+ {
+ return size.width * size.height;
+ }
+
+ // Find if any of those fit better than the frame for the preferred edge.
+ var bestEdge = metrics[0].edge;
+ var bestMetrics = metrics[0].metrics;
+ for (var i = 1; i < metrics.length; i++) {
+ var itemMetrics = metrics[i].metrics;
+ if (area(itemMetrics.contentSize) > area(bestMetrics.contentSize)) {
+ bestEdge = metrics[i].edge;
+ bestMetrics = itemMetrics;
+ }
+ }
+
+ var anchorPoint;
+ var bestFrame = bestMetrics.frame.round();
+
+ this._edge = bestEdge;
+
+ if (bestFrame === WebInspector.Rect.ZERO_RECT) {
+ // The target for the popover is offscreen.
+ this.dismiss();
+ } else {
+ switch (bestEdge) {
+ case WebInspector.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ anchorPoint = new WebInspector.Point(bestFrame.size.width - WebInspector.Popover.ShadowPadding, targetFrame.midY() - bestFrame.minY());
+ break;
+ case WebInspector.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ anchorPoint = new WebInspector.Point(WebInspector.Popover.ShadowPadding, targetFrame.midY() - bestFrame.minY());
+ break;
+ case WebInspector.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ anchorPoint = new WebInspector.Point(targetFrame.midX() - bestFrame.minX(), bestFrame.size.height - WebInspector.Popover.ShadowPadding);
+ break;
+ case WebInspector.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ anchorPoint = new WebInspector.Point(targetFrame.midX() - bestFrame.minX(), WebInspector.Popover.ShadowPadding);
+ break;
+ }
+
+ this._element.classList.add(this._cssClassNameForEdge());
+
+ if (shouldAnimate && this._edge === previousEdge)
+ this._animateFrame(bestFrame, anchorPoint);
+ else {
+ this.frame = bestFrame;
+ this._setAnchorPoint(anchorPoint);
+ this._drawBackground();
+ }
+
+ // Make sure content is centered in case either of the dimension is smaller than the minimal bounds.
+ if (this._preferredSize.width < WebInspector.Popover.MinWidth || this._preferredSize.height < WebInspector.Popover.MinHeight)
+ this._container.classList.add("center");
+ else
+ this._container.classList.remove("center");
+ }
+
+ // Wrap the content in the container so that it's located correctly.
+ if (this._contentNeedsUpdate) {
+ this._container.textContent = "";
+ this._content.replaceWith(this._container);
+ this._container.appendChild(this._content);
+ }
+
+ this._contentNeedsUpdate = false;
+ }
+
+ _cssClassNameForEdge()
+ {
+ switch (this._edge) {
+ case WebInspector.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ return "arrow-right";
+ case WebInspector.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ return "arrow-left";
+ case WebInspector.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ return "arrow-down";
+ case WebInspector.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ return "arrow-up";
+ }
+ console.error("Unknown edge.");
+ return "arrow-up";
+ }
+
+ _setAnchorPoint(anchorPoint)
+ {
+ anchorPoint.x = Math.floor(anchorPoint.x);
+ anchorPoint.y = Math.floor(anchorPoint.y);
+ this._anchorPoint = anchorPoint;
+ }
+
+ _animateFrame(toFrame, toAnchor)
+ {
+ var startTime = Date.now();
+ var duration = 350;
+ var epsilon = 1 / (200 * duration);
+ var spline = new WebInspector.CubicBezier(0.25, 0.1, 0.25, 1);
+
+ var fromFrame = this._frame.copy();
+ var fromAnchor = this._anchorPoint.copy();
+
+ function animatedValue(from, to, progress)
+ {
+ return from + (to - from) * progress;
+ }
+
+ function drawBackground()
+ {
+ var progress = spline.solve(Math.min((Date.now() - startTime) / duration, 1), epsilon);
+
+ this.frame = new WebInspector.Rect(
+ animatedValue(fromFrame.minX(), toFrame.minX(), progress),
+ animatedValue(fromFrame.minY(), toFrame.minY(), progress),
+ animatedValue(fromFrame.size.width, toFrame.size.width, progress),
+ animatedValue(fromFrame.size.height, toFrame.size.height, progress)
+ ).round();
+
+ this._setAnchorPoint(new WebInspector.Point(
+ animatedValue(fromAnchor.x, toAnchor.x, progress),
+ animatedValue(fromAnchor.y, toAnchor.y, progress)
+ ));
+
+ this._drawBackground();
+
+ if (progress < 1)
+ requestAnimationFrame(drawBackground.bind(this));
+ }
+
+ drawBackground.call(this);
+ }
+
+ _drawBackground()
+ {
+ var scaleFactor = window.devicePixelRatio;
+
+ var width = this._frame.size.width;
+ var height = this._frame.size.height;
+ var scaledWidth = width * scaleFactor;
+ var scaledHeight = height * scaleFactor;
+
+ // Create a scratch canvas so we can draw the popover that will later be drawn into
+ // the final context with a shadow.
+ var scratchCanvas = document.createElement("canvas");
+ scratchCanvas.width = scaledWidth;
+ scratchCanvas.height = scaledHeight;
+
+ var ctx = scratchCanvas.getContext("2d");
+ ctx.scale(scaleFactor, scaleFactor);
+
+ // Bounds of the path don't take into account the arrow, but really only the tight bounding box
+ // of the content contained within the frame.
+ var bounds;
+ var arrowHeight = WebInspector.Popover.AnchorSize.height;
+ switch (this._edge) {
+ case WebInspector.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ bounds = new WebInspector.Rect(0, 0, width - arrowHeight, height);
+ break;
+ case WebInspector.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ bounds = new WebInspector.Rect(arrowHeight, 0, width - arrowHeight, height);
+ break;
+ case WebInspector.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ bounds = new WebInspector.Rect(0, 0, width, height - arrowHeight);
+ break;
+ case WebInspector.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ bounds = new WebInspector.Rect(0, arrowHeight, width, height - arrowHeight);
+ break;
+ }
+
+ bounds = bounds.inset(WebInspector.Popover.ShadowEdgeInsets);
+
+ // Clip the frame.
+ ctx.fillStyle = "black";
+ this._drawFrame(ctx, bounds, this._edge, this._anchorPoint);
+ ctx.clip();
+
+ // Gradient fill, top-to-bottom.
+ var fillGradient = ctx.createLinearGradient(0, 0, 0, height);
+ fillGradient.addColorStop(0, "rgba(255, 255, 255, 0.95)");
+ fillGradient.addColorStop(1, "rgba(235, 235, 235, 0.95)");
+ ctx.fillStyle = fillGradient;
+ ctx.fillRect(0, 0, width, height);
+
+ // Stroke.
+ ctx.strokeStyle = "rgba(0, 0, 0, 0.25)";
+ ctx.lineWidth = 2;
+ this._drawFrame(ctx, bounds, this._edge, this._anchorPoint);
+ ctx.stroke();
+
+ // Draw the popover into the final context with a drop shadow.
+ var finalContext = document.getCSSCanvasContext("2d", this._canvasId, scaledWidth, scaledHeight);
+
+ finalContext.clearRect(0, 0, scaledWidth, scaledHeight);
+
+ finalContext.shadowOffsetX = 1;
+ finalContext.shadowOffsetY = 1;
+ finalContext.shadowBlur = 5;
+ finalContext.shadowColor = "rgba(0, 0, 0, 0.5)";
+
+ finalContext.drawImage(scratchCanvas, 0, 0, scaledWidth, scaledHeight);
+ }
+
+ _bestMetricsForEdge(preferredSize, targetFrame, containerFrame, edge)
+ {
+ var x, y;
+ var width = preferredSize.width + (WebInspector.Popover.ShadowPadding * 2) + (WebInspector.Popover.ContentPadding * 2);
+ var height = preferredSize.height + (WebInspector.Popover.ShadowPadding * 2) + (WebInspector.Popover.ContentPadding * 2);
+ var arrowLength = WebInspector.Popover.AnchorSize.height;
+
+ switch (edge) {
+ case WebInspector.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ width += arrowLength;
+ x = targetFrame.origin.x - width + WebInspector.Popover.ShadowPadding;
+ y = targetFrame.origin.y - (height - targetFrame.size.height) / 2;
+ break;
+ case WebInspector.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ width += arrowLength;
+ x = targetFrame.origin.x + targetFrame.size.width - WebInspector.Popover.ShadowPadding;
+ y = targetFrame.origin.y - (height - targetFrame.size.height) / 2;
+ break;
+ case WebInspector.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ height += arrowLength;
+ x = targetFrame.origin.x - (width - targetFrame.size.width) / 2;
+ y = targetFrame.origin.y - height + WebInspector.Popover.ShadowPadding;
+ break;
+ case WebInspector.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ height += arrowLength;
+ x = targetFrame.origin.x - (width - targetFrame.size.width) / 2;
+ y = targetFrame.origin.y + targetFrame.size.height - WebInspector.Popover.ShadowPadding;
+ break;
+ }
+
+ if (edge === WebInspector.RectEdge.MIN_X || edge === WebInspector.RectEdge.MAX_X) {
+ if (y < containerFrame.minY())
+ y = containerFrame.minY();
+ if (y + height > containerFrame.maxY())
+ y = containerFrame.maxY() - height;
+ } else {
+ if (x < containerFrame.minX())
+ x = containerFrame.minX();
+ if (x + width > containerFrame.maxX())
+ x = containerFrame.maxX() - width;
+ }
+
+ var preferredFrame = new WebInspector.Rect(x, y, width, height);
+ var bestFrame = preferredFrame.intersectionWithRect(containerFrame);
+
+ width = bestFrame.size.width - (WebInspector.Popover.ShadowPadding * 2);
+ height = bestFrame.size.height - (WebInspector.Popover.ShadowPadding * 2);
+
+ switch (edge) {
+ case WebInspector.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ case WebInspector.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ width -= arrowLength;
+ break;
+ case WebInspector.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ case WebInspector.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ height -= arrowLength;
+ break;
+ }
+
+ return {
+ frame: bestFrame,
+ contentSize: new WebInspector.Size(width, height)
+ };
+ }
+
+ _drawFrame(ctx, bounds, anchorEdge)
+ {
+ let cornerRadius = WebInspector.Popover.CornerRadius;
+ let arrowHalfLength = WebInspector.Popover.AnchorSize.width * 0.5;
+ let anchorPoint = this._anchorPoint;
+
+ // Prevent the arrow from being positioned against one of the popover's rounded corners.
+ let arrowPadding = cornerRadius + arrowHalfLength;
+ if (anchorEdge === WebInspector.RectEdge.MIN_Y || anchorEdge === WebInspector.RectEdge.MAX_Y)
+ anchorPoint.x = Number.constrain(anchorPoint.x, bounds.minX() + arrowPadding, bounds.maxX() - arrowPadding);
+ else
+ anchorPoint.y = Number.constrain(anchorPoint.y, bounds.minY() + arrowPadding, bounds.maxY() - arrowPadding);
+
+ ctx.beginPath();
+ switch (anchorEdge) {
+ case WebInspector.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ ctx.moveTo(bounds.maxX(), bounds.minY() + cornerRadius);
+ ctx.lineTo(bounds.maxX(), anchorPoint.y - arrowHalfLength);
+ ctx.lineTo(anchorPoint.x, anchorPoint.y);
+ ctx.lineTo(bounds.maxX(), anchorPoint.y + arrowHalfLength);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), cornerRadius);
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), cornerRadius);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), cornerRadius);
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), cornerRadius);
+ break;
+ case WebInspector.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ ctx.moveTo(bounds.minX(), bounds.maxY() - cornerRadius);
+ ctx.lineTo(bounds.minX(), anchorPoint.y + arrowHalfLength);
+ ctx.lineTo(anchorPoint.x, anchorPoint.y);
+ ctx.lineTo(bounds.minX(), anchorPoint.y - arrowHalfLength);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), cornerRadius);
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), cornerRadius);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), cornerRadius);
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), cornerRadius);
+ break;
+ case WebInspector.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ ctx.moveTo(bounds.maxX() - cornerRadius, bounds.maxY());
+ ctx.lineTo(anchorPoint.x + arrowHalfLength, bounds.maxY());
+ ctx.lineTo(anchorPoint.x, anchorPoint.y);
+ ctx.lineTo(anchorPoint.x - arrowHalfLength, bounds.maxY());
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), cornerRadius);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), cornerRadius);
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), cornerRadius);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), cornerRadius);
+ break;
+ case WebInspector.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ ctx.moveTo(bounds.minX() + cornerRadius, bounds.minY());
+ ctx.lineTo(anchorPoint.x - arrowHalfLength, bounds.minY());
+ ctx.lineTo(anchorPoint.x, anchorPoint.y);
+ ctx.lineTo(anchorPoint.x + arrowHalfLength, bounds.minY());
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), cornerRadius);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), cornerRadius);
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), cornerRadius);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), cornerRadius);
+ break;
+ }
+ ctx.closePath();
+ }
+
+ _addListenersIfNeeded()
+ {
+ if (!this._isListeningForPopoverEvents) {
+ this._isListeningForPopoverEvents = true;
+
+ window.addEventListener("mousedown", this, true);
+ window.addEventListener("scroll", this, true);
+ window.addEventListener("resize", this, true);
+ window.addEventListener("keypress", this, true);
+
+ WebInspector.quickConsole.keyboardShortcutDisabled = true;
+ }
+ }
+};
+
+WebInspector.Popover.FadeOutClassName = "fade-out";
+WebInspector.Popover.canvasId = 0;
+WebInspector.Popover.CornerRadius = 5;
+WebInspector.Popover.MinWidth = 40;
+WebInspector.Popover.MinHeight = 40;
+WebInspector.Popover.ShadowPadding = 5;
+WebInspector.Popover.ContentPadding = 5;
+WebInspector.Popover.AnchorSize = new WebInspector.Size(22, 11);
+WebInspector.Popover.ShadowEdgeInsets = new WebInspector.EdgeInsets(WebInspector.Popover.ShadowPadding);
+WebInspector.Popover.IgnoreAutoDismissClassName = "popover-ignore-auto-dismiss";
+WebInspector.Popover.EventPreventDismissSymbol = Symbol("popover-event-prevent-dismiss");