function appendBuilderQueueStatus(queue)
{
- this._appendPendingRevisionCount(queue)
+ this._appendPendingRevisionCount(queue);
var firstRecentFailedIteration = queue.firstRecentFailedIteration;
if (firstRecentFailedIteration && firstRecentFailedIteration.loaded) {
constructor: BuildbotQueueView,
__proto__: QueueView.prototype,
- _appendPendingRevisionCount: function(queue)
+ _latestFinishedIteration: function(queue)
{
for (var i = 0; i < queue.iterations.length; ++i) {
var iteration = queue.iterations[i];
if (!iteration.loaded || !iteration.finished)
continue;
+ return iteration;
+ }
+ return null;
+ },
+
+ _appendPendingRevisionCount: function(queue)
+ {
+ var latestFinishedIteration = this._latestFinishedIteration(queue);
+ if (!latestFinishedIteration)
+ return;
+
+ var latestRecordedOpenSourceRevisionNumber = webkitTrac.latestRecordedRevisionNumber;
+ if (!latestRecordedOpenSourceRevisionNumber)
+ return;
- var latestRecordedOpenSourceRevisionNumber = webkitTrac.latestRecordedRevisionNumber;
- if (!latestRecordedOpenSourceRevisionNumber)
+ var openSourceRevisionsBehind = latestRecordedOpenSourceRevisionNumber - latestFinishedIteration.openSourceRevision;
+ if (openSourceRevisionsBehind < 0)
+ openSourceRevisionsBehind = 0;
+
+ if (latestFinishedIteration.internalRevision) {
+ var latestRecordedInternalRevisionNumber = internalTrac.latestRecordedRevisionNumber;
+ if (!latestRecordedInternalRevisionNumber)
return;
- var openSourceRevisionsBehind = latestRecordedOpenSourceRevisionNumber - iteration.openSourceRevision;
- if (openSourceRevisionsBehind < 0)
- openSourceRevisionsBehind = 0;
-
- if (iteration.internalRevision) {
- var latestRecordedInternalRevisionNumber = internalTrac.latestRecordedRevisionNumber;
- if (!latestRecordedInternalRevisionNumber)
- return;
-
- var internalRevisionsBehind = latestRecordedInternalRevisionNumber - iteration.internalRevision;
- if (internalRevisionsBehind < 0)
- internalRevisionsBehind = 0;
- if (openSourceRevisionsBehind || internalRevisionsBehind) {
- var message = openSourceRevisionsBehind + " \uff0b " + internalRevisionsBehind + " revisions behind";
- var status = new StatusLineView(message, StatusLineView.Status.NoBubble);
- this.element.appendChild(status.element);
- }
- } else if (openSourceRevisionsBehind) {
- var message = openSourceRevisionsBehind + " " + (openSourceRevisionsBehind === 1 ? "revision behind" : "revisions behind");
+ var internalRevisionsBehind = latestRecordedInternalRevisionNumber - latestFinishedIteration.internalRevision;
+ if (internalRevisionsBehind < 0)
+ internalRevisionsBehind = 0;
+ if (openSourceRevisionsBehind || internalRevisionsBehind) {
+ var message = openSourceRevisionsBehind + " \uff0b " + internalRevisionsBehind + " revisions behind";
var status = new StatusLineView(message, StatusLineView.Status.NoBubble);
this.element.appendChild(status.element);
}
+ } else if (openSourceRevisionsBehind) {
+ var message = openSourceRevisionsBehind + " " + (openSourceRevisionsBehind === 1 ? "revision behind" : "revisions behind");
+ var status = new StatusLineView(message, StatusLineView.Status.NoBubble);
+ this.element.appendChild(status.element);
+ }
- return;
+ if (status)
+ new PopoverTracker(status.messageElement, this, queue);
+ },
+
+ presentPopoverForElement: function(element, popover, queue)
+ {
+ var latestFinishedIteration = this._latestFinishedIteration(queue);
+ if (!latestFinishedIteration)
+ return false;
+
+ function lineForCommit(trac, commit)
+ {
+ var result = document.createElement("div");
+ result.className = "pending-commit";
+
+ var linkElement = document.createElement("a");
+ linkElement.className = "revision";
+ linkElement.href = trac.revisionURL(commit.revisionNumber);
+ linkElement.target = "_blank";
+ linkElement.textContent = "r" + commit.revisionNumber;
+ result.appendChild(linkElement);
+
+ var authorElement = document.createElement("span");
+ authorElement.className = "author";
+ authorElement.textContent = commit.author;
+ result.appendChild(authorElement);
+
+ var titleElement = document.createElement("span");
+ titleElement.className = "title";
+ titleElement.innerHTML = commit.title.innerHTML;
+ result.appendChild(titleElement);
+
+ return result;
+ }
+
+ var content = document.createElement("div");
+ content.className = "pending-commits-popover";
+
+ for (var i = webkitTrac.recordedCommits.length - 1; i >= 0; --i) {
+ var commit = webkitTrac.recordedCommits[i];
+ if (commit.revisionNumber <= latestFinishedIteration.openSourceRevision)
+ break;
+
+ content.appendChild(lineForCommit(webkitTrac, commit));
}
+
+ if (latestFinishedIteration.internalRevision && internalTrac.latestRecordedRevisionNumber) {
+ if (latestFinishedIteration.internalRevision < internalTrac.latestRecordedRevisionNumber && content.hasChildNodes()) {
+ var divider = document.createElement("div");
+ divider.className = "divider";
+ content.appendChild(divider);
+ }
+
+ for (var i = internalTrac.recordedCommits.length - 1; i >= 0; --i) {
+ var commit = internalTrac.recordedCommits[i];
+ if (commit.revisionNumber <= latestFinishedIteration.internalRevision)
+ break;
+
+ content.appendChild(lineForCommit(internalTrac, commit));
+ }
+ }
+
+ var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
+ popover.present(rect, content, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
+
+ return true;
},
revisionLinksForIteration: function(iteration)
--- /dev/null
+/*
+ * 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.
+ */
+
+Dashboard.Point = function(x, y)
+{
+ this.x = x || 0;
+ this.y = y || 0;
+};
+
+Dashboard.Point.prototype = {
+ constructor: Dashboard.Point,
+
+ toString : function()
+ {
+ return "Dashboard.Point[" + this.x + "," + this.y + "]";
+ },
+
+ copy: function()
+ {
+ return new Dashboard.Point(this.x, this.y);
+ },
+
+ equals: function(anotherPoint)
+ {
+ return (this.x === anotherPoint.x && this.y === anotherPoint.y);
+ }
+};
+
+Dashboard.Size = function(width, height)
+{
+ this.width = width || 0;
+ this.height = height || 0;
+};
+
+Dashboard.Size.prototype = {
+ constructor: Dashboard.Size,
+
+ toString: function()
+ {
+ return "Dashboard.Size[" + this.width + "," + this.height + "]";
+ },
+
+ copy: function()
+ {
+ return new Dashboard.Size(this.width, this.height);
+ },
+
+ equals: function(anotherSize)
+ {
+ return (this.width === anotherSize.width && this.height === anotherSize.height);
+ }
+};
+
+Dashboard.Size.ZERO_SIZE = new Dashboard.Size(0, 0);
+
+
+Dashboard.Rect = function(x, y, width, height)
+{
+ this.origin = new Dashboard.Point(x || 0, y || 0);
+ this.size = new Dashboard.Size(width || 0, height || 0);
+};
+
+Dashboard.Rect.rectFromClientRect = function(clientRect)
+{
+ return new Dashboard.Rect(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
+};
+
+Dashboard.Rect.prototype = {
+ constructor: Dashboard.Rect,
+
+ toString: function()
+ {
+ return "Dashboard.Rect[" + [this.origin.x, this.origin.y, this.size.width, this.size.height].join(", ") + "]";
+ },
+
+ copy: function()
+ {
+ return new Dashboard.Rect(this.origin.x, this.origin.y, this.size.width, this.size.height);
+ },
+
+ equals: function(anotherRect)
+ {
+ return (this.origin.equals(anotherRect.origin) && this.size.equals(anotherRect.size));
+ },
+
+ inset: function(insets)
+ {
+ return new Dashboard.Rect(
+ this.origin.x + insets.left,
+ this.origin.y + insets.top,
+ this.size.width - insets.left - insets.right,
+ this.size.height - insets.top - insets.bottom
+ );
+ },
+
+ pad: function(padding)
+ {
+ return new Dashboard.Rect(
+ this.origin.x - padding,
+ this.origin.y - padding,
+ this.size.width + padding * 2,
+ this.size.height + padding * 2
+ );
+ },
+
+ minX: function()
+ {
+ return this.origin.x;
+ },
+
+ minY: function()
+ {
+ return this.origin.y;
+ },
+
+ midX: function()
+ {
+ return this.origin.x + (this.size.width / 2);
+ },
+
+ midY: function()
+ {
+ return this.origin.y + (this.size.height / 2);
+ },
+
+ maxX: function()
+ {
+ return this.origin.x + this.size.width;
+ },
+
+ maxY: function()
+ {
+ return this.origin.y + this.size.height;
+ },
+
+ intersectionWithRect: function(rect)
+ {
+ var x1 = Math.max(this.minX(), rect.minX());
+ var x2 = Math.min(this.maxX(), rect.maxX());
+ if (x1 > x2)
+ return Dashboard.Rect.ZERO_RECT;
+ var intersection = new Dashboard.Rect;
+ intersection.origin.x = x1;
+ intersection.size.width = x2 - x1;
+ var y1 = Math.max(this.minY(), rect.minY());
+ var y2 = Math.min(this.maxY(), rect.maxY());
+ if (y1 > y2)
+ return Dashboard.Rect.ZERO_RECT;
+ intersection.origin.y = y1;
+ intersection.size.height = y2 - y1;
+ return intersection;
+ },
+
+ containsPoint: function(point)
+ {
+ return point.x >= this.minX() && point.x <= this.maxX()
+ && point.y >= this.minY() && point.y <= this.maxY();
+ }
+};
+
+Dashboard.Rect.ZERO_RECT = new Dashboard.Rect(0, 0, 0, 0);
+
+
+Dashboard.EdgeInsets = function(top, right, bottom, left)
+{
+ console.assert(arguments.length === 1 || arguments.length === 4);
+
+ if (arguments.length === 1) {
+ this.top = top;
+ this.right = top;
+ this.bottom = top;
+ this.left = top;
+ } else if (arguments.length === 4) {
+ this.top = top;
+ this.right = right;
+ this.bottom = bottom;
+ this.left = left;
+ }
+};
+
+Dashboard.EdgeInsets.prototype = {
+ constructor: Dashboard.EdgeInsets,
+
+ equals: function(anotherInset)
+ {
+ return (this.top === anotherInset.top && this.right === anotherInset.right &&
+ this.bottom === anotherInset.bottom && this.left === anotherInset.left);
+ },
+
+ copy: function()
+ {
+ return new Dashboard.EdgeInsets(this.top, this.right, this.bottom, this.left);
+ }
+};
+
+Dashboard.RectEdge = {
+ MIN_X : 0,
+ MIN_Y : 1,
+ MAX_X : 2,
+ MAX_Y : 3
+};
--- /dev/null
+/*
+ * 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.
+ */
+
+Dashboard.Popover = function(delegate)
+{
+ BaseObject.call(this);
+
+ this.delegate = delegate;
+ this._edge = null;
+ this._frame = new Dashboard.Rect;
+ this._content = null;
+ this._targetFrame = new Dashboard.Rect;
+ this._preferredEdges = null;
+
+ this._element = document.createElement("div");
+ this._element.className = Dashboard.Popover.StyleClassName;
+ this._canvasId = "popover-" + (Dashboard.Popover.canvasId++);
+ 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";
+};
+
+Dashboard.Popover.StyleClassName = "popover";
+Dashboard.Popover.VisibleClassName = "visible";
+Dashboard.Popover.StepInClassName = "step-in";
+Dashboard.Popover.FadeOutClassName = "fade-out";
+Dashboard.Popover.PreventDocumentScrollingClassName = "popover-prevent-document-scrolling";
+Dashboard.Popover.canvasId = 0;
+Dashboard.Popover.CornerRadius = 5;
+Dashboard.Popover.MinWidth = 40;
+Dashboard.Popover.MinHeight = 40;
+Dashboard.Popover.ShadowPadding = 5;
+Dashboard.Popover.ContentPadding = 5;
+Dashboard.Popover.AnchorSize = new Dashboard.Size(22, 11);
+Dashboard.Popover.ShadowEdgeInsets = new Dashboard.EdgeInsets(Dashboard.Popover.ShadowPadding);
+
+BaseObject.addConstructorFunctions(Dashboard.Popover);
+
+Dashboard.Popover.prototype = {
+ constructor: Dashboard.Popover,
+ __proto__: BaseObject.prototype,
+
+ // Public
+
+ get element()
+ {
+ return this._element;
+ },
+
+ get frame()
+ {
+ return this._frame;
+ },
+
+ set frame(frame)
+ {
+ this._element.style.left = window.scrollX + frame.origin.x + "px";
+ this._element.style.top = window.scrollY + frame.origin.y + "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;
+ },
+
+ get visible()
+ {
+ return this._element.parentNode === document.body
+ && !this._element.classList.contains(Dashboard.Popover.StepInClassName);
+ },
+
+ /**
+ * @param {Dashboard.Rect} targetFrame
+ * @param {Element} content
+ * @param {Dashboard.RectEdge}[] preferredEdges
+ */
+ present: function(targetFrame, content, preferredEdges)
+ {
+ console.assert(!this._content)
+
+ this._targetFrame = targetFrame;
+ this._content = content;
+ this._preferredEdges = preferredEdges;
+
+ window.addEventListener("mousedown", this, true);
+ window.addEventListener("scroll", this, true);
+
+ this._update();
+
+ this._element.classList.add(Dashboard.Popover.StepInClassName);
+
+ // Scrolling inside a popover should not cascade to document when reaching a bound, because that would make it disappear unexpectedly.
+ if (this._container.offsetHeight < this._container.scrollHeight) {
+ this._element.addEventListener("mouseenter", this, true);
+ this._element.addEventListener("mouseleave", this, true);
+ }
+ },
+
+ makeVisibleImmediately: function()
+ {
+ console.assert(this._content);
+
+ this._finalizePresentation();
+ },
+
+ dismiss: function()
+ {
+ if (this._element.parentNode !== document.body)
+ return;
+
+ this._element.classList.add(Dashboard.Popover.FadeOutClassName);
+ },
+
+ dismissImmediately: function()
+ {
+ if (this._element.parentNode !== document.body)
+ return;
+
+ this._finalizeDismissal();
+ },
+
+ handleEvent: function(event)
+ {
+ switch (event.type) {
+ case "mousedown":
+ case "scroll":
+ if (!this._element.contains(event.target))
+ this.dismissImmediately();
+ break;
+ case "transitionend":
+ if (this._element.classList.contains(Dashboard.Popover.StepInClassName))
+ this._finalizePresentation();
+ else if (this._element.classList.contains(Dashboard.Popover.FadeOutClassName))
+ this._finalizeDismissal();
+ break;
+ case "mouseenter":
+ document.body.classList.add(Dashboard.Popover.PreventDocumentScrollingClassName);
+ break;
+ case "mouseleave":
+ if (!this._element.isSelfOrAncestor(event.toElement))
+ document.body.classList.remove(Dashboard.Popover.PreventDocumentScrollingClassName);
+ break;
+ }
+ },
+
+ // Private
+
+ _update: function()
+ {
+ 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(Dashboard.Popover.FadeOutClassName);
+
+ // 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._element.replaceChild(this._content, this._container);
+
+ // Get the ideal size for the popover to fit its content.
+ var popoverBounds = this._element.getBoundingClientRect();
+ this._preferredSize = new Dashboard.Size(Math.ceil(popoverBounds.width), Math.ceil(popoverBounds.height));
+
+ // The frame of the window with a little inset to make sure we have room for shadows.
+ var containerFrame = new Dashboard.Rect(0, 0, window.innerWidth, window.innerHeight);
+ containerFrame = containerFrame.inset(Dashboard.Popover.ShadowEdgeInsets);
+
+ // Work out the metrics for all edges.
+ var metrics = new Array(preferredEdges.length);
+ for (var edgeName in Dashboard.RectEdge) {
+ var edge = Dashboard.RectEdge[edgeName];
+ var item = {
+ edge: 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;
+
+ this.frame = bestFrame;
+ this._edge = bestEdge;
+
+ if (this.frame === Dashboard.Rect.ZERO_RECT) {
+ // The target for the popover is offscreen.
+ this.dismiss();
+ } else {
+ switch (bestEdge) {
+ case Dashboard.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ anchorPoint = new Dashboard.Point(bestFrame.size.width - Dashboard.Popover.ShadowPadding, targetFrame.midY() - bestFrame.minY());
+ break;
+ case Dashboard.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ anchorPoint = new Dashboard.Point(Dashboard.Popover.ShadowPadding, targetFrame.midY() - bestFrame.minY());
+ break;
+ case Dashboard.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ anchorPoint = new Dashboard.Point(targetFrame.midX() - bestFrame.minX(), bestFrame.size.height - Dashboard.Popover.ShadowPadding);
+ break;
+ case Dashboard.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ anchorPoint = new Dashboard.Point(targetFrame.midX() - bestFrame.minX(), Dashboard.Popover.ShadowPadding);
+ break;
+ }
+
+ this._element.classList.add(this._cssClassNameForEdge());
+
+ this._drawBackground(bestEdge, anchorPoint);
+
+ // Make sure content is centered in case either of the dimension is smaller than the minimal bounds.
+ if (this._preferredSize.width < Dashboard.Popover.MinWidth || this._preferredSize.height < Dashboard.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.
+ this._container.textContent = "";
+ this._element.replaceChild(this._container, this._content);
+ this._container.appendChild(this._content);
+ },
+
+ _cssClassNameForEdge: function()
+ {
+ switch (this._edge) {
+ case Dashboard.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ return "arrow-right";
+ case Dashboard.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ return "arrow-left";
+ case Dashboard.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ return "arrow-down";
+ case Dashboard.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ return "arrow-up";
+ }
+ console.error("Unknown edge.");
+ return "arrow-up";
+ },
+
+ _drawBackground: function(edge, anchorPoint)
+ {
+ 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 = Dashboard.Popover.AnchorSize.height;
+ switch (edge) {
+ case Dashboard.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ bounds = new Dashboard.Rect(0, 0, width - arrowHeight, height);
+ break;
+ case Dashboard.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ bounds = new Dashboard.Rect(arrowHeight, 0, width - arrowHeight, height);
+ break;
+ case Dashboard.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ bounds = new Dashboard.Rect(0, 0, width, height - arrowHeight);
+ break;
+ case Dashboard.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ bounds = new Dashboard.Rect(0, arrowHeight, width, height - arrowHeight);
+ break;
+ }
+
+ bounds = bounds.inset(Dashboard.Popover.ShadowEdgeInsets);
+
+ // Clip the frame.
+ ctx.fillStyle = "black";
+ this._drawFrame(ctx, bounds, edge, 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, edge, 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: function(preferredSize, targetFrame, containerFrame, edge)
+ {
+ var x, y;
+ var width = preferredSize.width + (Dashboard.Popover.ShadowPadding * 2) + (Dashboard.Popover.ContentPadding * 2);
+ var height = preferredSize.height + (Dashboard.Popover.ShadowPadding * 2) + (Dashboard.Popover.ContentPadding * 2);
+ var arrowLength = Dashboard.Popover.AnchorSize.height;
+
+ switch (edge) {
+ case Dashboard.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ width += arrowLength;
+ x = targetFrame.origin.x - width + Dashboard.Popover.ShadowPadding;
+ y = targetFrame.origin.y - (height - targetFrame.size.height) / 2;
+ break;
+ case Dashboard.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ width += arrowLength;
+ x = targetFrame.origin.x + targetFrame.size.width - Dashboard.Popover.ShadowPadding;
+ y = targetFrame.origin.y - (height - targetFrame.size.height) / 2;
+ break;
+ case Dashboard.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 + Dashboard.Popover.ShadowPadding;
+ break;
+ case Dashboard.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 - Dashboard.Popover.ShadowPadding;
+ break;
+ }
+
+ if (edge === Dashboard.RectEdge.MIN_X || edge === Dashboard.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 Dashboard.Rect(x, y, width, height);
+ var bestFrame = preferredFrame.intersectionWithRect(containerFrame);
+
+ width = bestFrame.size.width - (Dashboard.Popover.ShadowPadding * 2);
+ height = bestFrame.size.height - (Dashboard.Popover.ShadowPadding * 2);
+
+ switch (edge) {
+ case Dashboard.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ case Dashboard.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ width -= arrowLength;
+ break;
+ case Dashboard.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ case Dashboard.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ height -= arrowLength;
+ break;
+ }
+
+ return {
+ frame: bestFrame,
+ contentSize: new Dashboard.Size(width, height)
+ };
+ },
+
+ _drawFrame: function(ctx, bounds, anchorEdge, anchorPoint)
+ {
+ var r = Dashboard.Popover.CornerRadius;
+ var arrowHalfLength = Dashboard.Popover.AnchorSize.width / 2;
+
+ ctx.beginPath();
+ switch (anchorEdge) {
+ case Dashboard.RectEdge.MIN_X: // Displayed on the left of the target, arrow points right.
+ ctx.moveTo(bounds.maxX(), bounds.minY() + r);
+ 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(), r);
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), r);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), r);
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), r);
+ break;
+ case Dashboard.RectEdge.MAX_X: // Displayed on the right of the target, arrow points left.
+ ctx.moveTo(bounds.minX(), bounds.maxY() - r);
+ 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(), r);
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), r);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), r);
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), r);
+ break;
+ case Dashboard.RectEdge.MIN_Y: // Displayed above the target, arrow points down.
+ ctx.moveTo(bounds.maxX() - r, 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(), r);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), r);
+ ctx.arcTo(bounds.maxX(), bounds.minY(), bounds.maxX(), bounds.maxY(), r);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), r);
+ break;
+ case Dashboard.RectEdge.MAX_Y: // Displayed below the target, arrow points up.
+ ctx.moveTo(bounds.minX() + r, 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(), r);
+ ctx.arcTo(bounds.maxX(), bounds.maxY(), bounds.minX(), bounds.maxY(), r);
+ ctx.arcTo(bounds.minX(), bounds.maxY(), bounds.minX(), bounds.minY(), r);
+ ctx.arcTo(bounds.minX(), bounds.minY(), bounds.maxX(), bounds.minY(), r);
+ break;
+ }
+ ctx.closePath();
+ },
+
+ _finalizePresentation: function()
+ {
+ var wasVisible = this.visible;
+ this._element.classList.remove(Dashboard.Popover.StepInClassName);
+ this._element.classList.remove(Dashboard.Popover.FadeOutClassName);
+ this._element.classList.add(Dashboard.Popover.VisibleClassName);
+
+ // Make scroll bar flash if present, so that the user sees that scrolling is possible.
+ // FIXME: Is there a better way?
+ if (!wasVisible) {
+ this._container.scrollTop = 1;
+ this._container.scrollTop = 0;
+ }
+ },
+
+ _finalizeDismissal: function()
+ {
+ window.removeEventListener("mousedown", this, true);
+ window.removeEventListener("scroll", this, true);
+
+ document.body.removeChild(this._element);
+ document.body.classList.remove(Dashboard.Popover.PreventDocumentScrollingClassName);
+ this._element.classList.remove(Dashboard.Popover.VisibleClassName);
+ this._element.classList.remove(Dashboard.Popover.StepInClassName);
+ this._element.classList.remove(Dashboard.Popover.FadeOutClassName);
+ this._container.textContent = "";
+ if (this.delegate && typeof this.delegate.didDismissPopover === "function")
+ this.delegate.didDismissPopover(this);
+ }
+};
--- /dev/null
+/*
+ * 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.
+ */
+
+PopoverTracker = function(element, delegate, context)
+{
+ BaseObject.call(this);
+
+ this._element = element;
+ this._delegate = delegate;
+ this._context = context;
+ this._active = false;
+
+ element.addEventListener("mouseenter", this._mouseEnteredPopoverOrElement.bind(this), true);
+ element.addEventListener("mouseleave", this._mouseExitedPopoverOrElement.bind(this), true);
+};
+
+PopoverTracker._popover = null; // Only one popover may be active at any time.
+
+BaseObject.addConstructorFunctions(PopoverTracker);
+
+PopoverTracker.prototype = {
+ constructor: PopoverTracker,
+ __proto__: BaseObject.prototype,
+
+ _mouseEnteredPopoverOrElement: function(event)
+ {
+ var popover = PopoverTracker._popover;
+ var popoverWasVisible = popover && popover.visible;
+ if (popover) {
+ // Abort fade-out when re-entering the same element or an existing popover.
+ if ((this._active && this._element.isSelfOrAncestor(event.toElement))
+ || popover.element.isSelfOrAncestor(event.toElement)) {
+ popover.makeVisibleImmediately();
+ return;
+ }
+
+ // We entered a different element, dismiss the old popover.
+ popover.dismissImmediately();
+ }
+ console.assert(!PopoverTracker._popover);
+
+ if (!this._delegate || !this._delegate.presentPopoverForElement || typeof this._delegate.presentPopoverForElement != "function")
+ return;
+
+ var popover = new Dashboard.Popover(this);
+ if (!this._delegate.presentPopoverForElement(event.target, popover, this._context))
+ return;
+
+ if (popoverWasVisible)
+ popover.makeVisibleImmediately();
+
+ this._active = true;
+ PopoverTracker._popover = popover;
+ popover.element.addEventListener("mouseenter", this._mouseEnteredPopoverOrElement.bind(this), true);
+ popover.element.addEventListener("mouseleave", this._mouseExitedPopoverOrElement.bind(this), true);
+ },
+
+ _mouseExitedPopoverOrElement: function(event)
+ {
+ var popover = PopoverTracker._popover;
+
+ if (!popover)
+ return;
+
+ if (this._element.isSelfOrAncestor(event.toElement) || popover.element.isSelfOrAncestor(event.toElement))
+ return;
+
+ if (popover.visible)
+ popover.dismiss();
+ else
+ popover.dismissImmediately();
+ },
+
+ didDismissPopover: function(popover)
+ {
+ console.assert(popover === PopoverTracker._popover);
+ this._active = false;
+ PopoverTracker._popover = null;
+ }
+};
} else {
this._messageElement.textContent = x;
}
+ },
+
+ get messageElement()
+ {
+ return this._messageElement;
}
};
var link = doc.evaluate("./link", commitElement, null, XPathResult.STRING_TYPE).stringValue;
var revisionNumber = parseInt(/\d+$/.exec(link))
- var title = doc.evaluate("./title", commitElement, null, XPathResult.STRING_TYPE).stringValue;
- title = title.replace(/^Changeset \[\d+\]: /, "");
- var author = doc.evaluate("./author", commitElement, null, XPathResult.STRING_TYPE).stringValue;
+ function tracNSResolver(prefix)
+ {
+ if (prefix == "dc")
+ return "http://purl.org/dc/elements/1.1/";
+ return null;
+ }
+
+ var author = doc.evaluate("./author|dc:creator", commitElement, tracNSResolver, XPathResult.STRING_TYPE).stringValue;
var date = doc.evaluate("./pubDate", commitElement, null, XPathResult.STRING_TYPE).stringValue;
date = new Date(Date.parse(date));
var description = doc.evaluate("./description", commitElement, null, XPathResult.STRING_TYPE).stringValue;
+ // The feed contains a <title>, but it's not parsed as well as what we are getting from description.
+ var parsedDescription = document.createElement("div");
+ parsedDescription.innerHTML = description;
+ var title = document.createElement("div");
+ var node = parsedDescription.firstChild.firstChild;
+ while (node && node.tagName != "BR") {
+ title.appendChild(node.cloneNode(true));
+ node = node.nextSibling;
+ }
+
+ // For some reason, trac titles start with a newline. Delete it.
+ if (title.firstChild && title.firstChild.nodeType == Node.TEXT_NODE && title.firstChild.textContent.length > 0 && title.firstChild.textContent[0] == "\n")
+ title.firstChild.textContent = title.firstChild.textContent.substring(1);
+
return {
revisionNumber: revisionNumber,
link: link,
request.send();
};
+Node.prototype.isAncestor = function(node)
+{
+ if (!node)
+ return false;
+
+ var currentNode = node.parentNode;
+ while (currentNode) {
+ if (this === currentNode)
+ return true;
+ currentNode = currentNode.parentNode;
+ }
+
+ return false;
+}
+
+Node.prototype.isDescendant = function(descendant)
+{
+ return !!descendant && descendant.isAncestor(this);
+}
+
+Node.prototype.isSelfOrAncestor = function(node)
+{
+ return !!node && (node === this || this.isAncestor(node));
+}
+
+Node.prototype.isSelfOrDescendant = function(node)
+{
+ return !!node && (node === this || this.isDescendant(node));
+}
+
Element.prototype.removeChildren = function()
{
// This has been tested to be the fastest removal method.
this.textContent = "";
};
+DOMTokenList.prototype.contains = function(string)
+{
+ for (var i = 0, end = this.length; i < end; ++i) {
+ if (this.item(i) === string)
+ return true;
+ }
+ return false;
+}
+
Array.prototype.contains = function(value)
{
return this.indexOf(value) >= 0;
--- /dev/null
+/*
+ * 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.
+ */
+
+.popover {
+ position: absolute;
+ min-width: 20px;
+ min-height: 20px;
+ box-sizing: border-box;
+ pointer-events: none;
+ z-index: 1000;
+ opacity: 0;
+}
+
+.popover.arrow-up {
+ padding: 16px 5px 5px 5px;
+}
+
+.popover.arrow-right {
+ padding: 5px 16px 5px 5px;
+}
+
+.popover.arrow-down {
+ padding: 5px 5px 16px 5px;
+}
+
+.popover.arrow-left {
+ padding: 5px 5px 5px 16px;
+}
+
+.popover.visible {
+ opacity: 1;
+}
+
+.popover.step-in {
+ transition: opacity 0ms;
+ transition-delay: 1s;
+ opacity: 1;
+}
+
+.popover.fade-out {
+ transition: opacity 350ms;
+ transition-delay: 1s;
+ opacity: 0;
+}
+
+.popover > .container {
+ position: absolute;
+ left: 5px;
+ top: 5px;
+ right: 5px;
+ bottom: 5px;
+
+ padding: 5px;
+
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ pointer-events: auto;
+}
+
+.popover > .container.center {
+ display: -webkit-flex;
+ -webkit-justify-content: center;
+ -webkit-align-items: center;
+}
+
+.popover.arrow-up > .container {
+ top: 16px;
+}
+
+.popover.arrow-right > .container {
+ right: 16px;
+}
+
+.popover.arrow-down > .container {
+ bottom: 16px;
+}
+
+.popover.arrow-left > .container {
+ left: 16px;
+}
+
+body.popover-prevent-document-scrolling {
+ overflow: hidden;
+}
.queue-view .queueLabel:not(:first-child) {
margin-top: 10px;
}
+
+.pending-commits-popover > .pending-commit {
+ font-family: "HelveticaNeue-Light", "Helvetica Neue", sans-serif;
+ color: rgb(145, 135, 95);
+ font-size: 12px;
+ text-align: left;
+ padding: 1px 6px 1px 6px;
+ user-select: auto;
+}
+
+.pending-commits-popover > .pending-commit > .author {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.pending-commits-popover > .pending-commit > .title {
+ color: black;
+}
+
+.pending-commits-popover > .divider {
+ height: 7px;
+}
line-height: 13px;
}
+.status-line.no-bubble .message {
+ display: inline-block; /* Fit block to content, so that popovers are positioned correctly. */
+}
+
.status-line.neutral .label,
.status-line.neutral .message,
.status-line.no-bubble .label,
<title>WebKit Bot Watcher's Dashboard</title>
<link rel="stylesheet" href="Styles/Main.css"></link>
+ <link rel="stylesheet" href="Styles/Popover.css"></link>
<link rel="stylesheet" href="Styles/QueueView.css"></link>
<link rel="stylesheet" href="Styles/StatusLineView.css"></link>
<script src="Scripts/EWSQueue.js"></script>
<script src="Scripts/BuildbotIteration.js"></script>
<script src="Scripts/BuildbotTestResults.js"></script>
+ <script src="Scripts/Geometry.js"></script>
+ <script src="Scripts/Popover.js"></script>
+ <script src="Scripts/PopoverTracker.js"></script>
<script src="Scripts/QueueView.js"></script>
<script src="Scripts/BuildbotQueueView.js"></script>
<script src="Scripts/BuildbotBuilderQueueView.js"></script>
+2013-12-29 Alexey Proskuryakov <ap@apple.com>
+
+ Please display information about pending runs in build.webkit.org/dashboard
+ https://bugs.webkit.org/show_bug.cgi?id=122180
+
+ Reviewed by Timothy Hatcher.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/BuildbotBuilderQueueView.js:
+ (BuildbotBuilderQueueView.prototype.update.appendBuilderQueueStatus): Added a semicolon at the end of a line.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/BuildbotQueueView.js:
+ (BuildbotQueueView.prototype._latestFinishedIteration): Factored out of _appendPendingRevisionCount.
+ (BuildbotQueueView.prototype._appendPendingRevisionCount): Install a popover tracker over the element.
+ (BuildbotQueueView.prototype.lineForCommit): Build an element for a particular commit ot be shown in popover.
+ (BuildbotQueueView.prototype.presentPopoverForElement): Build and show popover content when PopoverTracker
+ asks us to.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/Geometry.js: Added.
+ Taken from WebInspector with minimal changes:
+ - Changed root name from WebInspector to Dashboard.
+ - Removed some unused functionality.
+ - Added Rect.containsPoint.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/Popover.js: Added.
+ Popover has extensive changes compared to WebInspector version, only drawing code is the same:
+ - Fixed to work in scrollable pages - WebInspector version assumes that window
+ and document coordinates are the same, and also erroneously dismisses a scrollable
+ popover if scrolling cascades out of it after reaching a bound.
+ - Simplified API and implementation to Dashboard needs, it is no longer possible to
+ change content of an existing popover.
+ - Rewrote visibility tracking to be more complete, and not rely on external tracker
+ object so much.
+ - Added code to flash scroll bars when showing a scrollable popover.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/PopoverTracker.js: Added.
+ Objects of this class show and hide popovers as appropriate for registered active elements.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/StatusLineView.js:
+ (StatusLineView.prototype.get messageElement): Added an accessor, so that we could
+ install a popover on message element. It's the only visible element in pending commit
+ line, but the line has different bounds, so we can't install a popover on it (it
+ would be incorrectly positioned if we did).
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/Trac.js:
+ (Trac.prototype._convertCommitInfoElementToObject):
+ - Some trac installations report author in a different element, updated to support that.
+ - Changed to parse title out of description, because trac titles are ugly. Also,
+ we get a nice HTML with links from the description.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Scripts/Utilities.js:
+ (Node.prototype.isAncestor): Copied from Web Inspector, only changing the form for
+ consistency with the rest of this file (add a property on prototype with assignment
+ instead of using Object.defineProperty).
+ (Node.prototype.isDescendant): Ditto.
+ (Node.prototype.isSelfOrAncestor): Ditto.
+ (Node.prototype.isSelfOrDescendant): Ditto.
+ (DOMTokenList.prototype.contains): Ditto.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Styles/Popover.css: Added.
+ Like JS counterpart, mostly lifted from Web Inspector.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Styles/QueueView.css:
+ Added style rules for pending commits popover.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/Styles/StatusLineView.css:
+ (.status-line.no-bubble .message): Changed to display:inline-block, so that it fits
+ to content, and we can show the popover in a correct place.
+
+ * BuildSlaveSupport/build.webkit.org-config/public_html/dashboard/index.html: Added new files.
+
2013-12-27 Gavin Barraclough <barraclough@apple.com>
Merge PageVisibilityState & ViewState::IsVisible in WebKit2