1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.TimelineRuler = function() 27{ 28 WebInspector.Object.call(this); 29 30 this._element = document.createElement("div"); 31 this._element.className = WebInspector.TimelineRuler.StyleClassName; 32 33 this._headerElement = document.createElement("div"); 34 this._headerElement.className = WebInspector.TimelineRuler.HeaderElementStyleClassName; 35 this._element.appendChild(this._headerElement); 36 37 this._markersElement = document.createElement("div"); 38 this._markersElement.className = WebInspector.TimelineRuler.MarkersElementStyleClassName; 39 this._element.appendChild(this._markersElement); 40 41 this._zeroTime = 0; 42 this._startTime = 0; 43 this._endTime = 0; 44 this._duration = NaN; 45 this._secondsPerPixel = 0; 46 this._selectionStartTime = 0; 47 this._selectionEndTime = Infinity; 48 this._endTimePinned = false; 49 this._allowsClippedLabels = false; 50 this._allowsTimeRangeSelection = false; 51 52 this._markerElementMap = new Map; 53} 54 55WebInspector.TimelineRuler.MinimumLeftDividerSpacing = 48; 56WebInspector.TimelineRuler.MinimumDividerSpacing = 64; 57 58WebInspector.TimelineRuler.StyleClassName = "timeline-ruler"; 59WebInspector.TimelineRuler.AllowsTimeRangeSelectionStyleClassName = "allows-time-range-selection"; 60WebInspector.TimelineRuler.HeaderElementStyleClassName = "header"; 61WebInspector.TimelineRuler.DividerElementStyleClassName = "divider"; 62WebInspector.TimelineRuler.DividerLabelElementStyleClassName = "label"; 63 64WebInspector.TimelineRuler.MarkersElementStyleClassName = "markers"; 65WebInspector.TimelineRuler.BaseMarkerElementStyleClassName = "marker"; 66WebInspector.TimelineRuler.ShadedAreaElementStyleClassName = "shaded-area"; 67WebInspector.TimelineRuler.SelectionDragElementStyleClassName = "selection-drag"; 68WebInspector.TimelineRuler.SelectionHandleElementStyleClassName = "selection-handle"; 69WebInspector.TimelineRuler.LeftSelectionElementStyleClassName = "left"; 70WebInspector.TimelineRuler.RightSelectionElementStyleClassName = "right"; 71WebInspector.TimelineRuler.MinimumSelectionTimeRange = 0.01; 72 73WebInspector.TimelineRuler.Event = { 74 TimeRangeSelectionChanged: "time-ruler-time-range-selection-changed" 75}; 76 77WebInspector.TimelineRuler.prototype = { 78 constructor: WebInspector.TimelineRuler, 79 80 // Public 81 82 get element() 83 { 84 return this._element; 85 }, 86 87 get allowsClippedLabels() 88 { 89 return this._allowsClippedLabels 90 }, 91 92 set allowsClippedLabels(x) 93 { 94 if (this._allowsClippedLabels === x) 95 return; 96 97 this._allowsClippedLabels = x || false; 98 99 this._needsLayout(); 100 }, 101 102 get allowsTimeRangeSelection() 103 { 104 return this._allowsTimeRangeSelection; 105 }, 106 107 set allowsTimeRangeSelection(x) 108 { 109 if (this._allowsTimeRangeSelection === x) 110 return; 111 112 this._allowsTimeRangeSelection = x || false; 113 114 if (x) { 115 this._mouseDownEventListener = this._handleMouseDown.bind(this); 116 this._element.addEventListener("mousedown", this._mouseDownEventListener); 117 118 this._leftShadedAreaElement = document.createElement("div"); 119 this._leftShadedAreaElement.classList.add(WebInspector.TimelineRuler.ShadedAreaElementStyleClassName); 120 this._leftShadedAreaElement.classList.add(WebInspector.TimelineRuler.LeftSelectionElementStyleClassName); 121 122 this._rightShadedAreaElement = document.createElement("div"); 123 this._rightShadedAreaElement.classList.add(WebInspector.TimelineRuler.ShadedAreaElementStyleClassName); 124 this._rightShadedAreaElement.classList.add(WebInspector.TimelineRuler.RightSelectionElementStyleClassName); 125 126 this._leftSelectionHandleElement = document.createElement("div"); 127 this._leftSelectionHandleElement.classList.add(WebInspector.TimelineRuler.SelectionHandleElementStyleClassName); 128 this._leftSelectionHandleElement.classList.add(WebInspector.TimelineRuler.LeftSelectionElementStyleClassName); 129 this._leftSelectionHandleElement.addEventListener("mousedown", this._handleSelectionHandleMouseDown.bind(this)); 130 131 this._rightSelectionHandleElement = document.createElement("div"); 132 this._rightSelectionHandleElement.classList.add(WebInspector.TimelineRuler.SelectionHandleElementStyleClassName); 133 this._rightSelectionHandleElement.classList.add(WebInspector.TimelineRuler.RightSelectionElementStyleClassName); 134 this._rightSelectionHandleElement.addEventListener("mousedown", this._handleSelectionHandleMouseDown.bind(this)); 135 136 this._selectionDragElement = document.createElement("div"); 137 this._selectionDragElement.classList.add(WebInspector.TimelineRuler.SelectionDragElementStyleClassName); 138 139 this._needsSelectionLayout(); 140 } else { 141 this._element.removeEventListener("mousedown", this._mouseDownEventListener); 142 delete this._mouseDownEventListener; 143 144 this._leftShadedAreaElement.remove(); 145 this._rightShadedAreaElement.remove(); 146 this._leftSelectionHandleElement.remove(); 147 this._rightSelectionHandleElement.remove(); 148 this._selectionDragElement.remove(); 149 150 delete this._leftShadedAreaElement; 151 delete this._rightShadedAreaElement; 152 delete this._leftSelectionHandleElement; 153 delete this._rightSelectionHandleElement; 154 delete this._selectionDragElement; 155 } 156 }, 157 158 get zeroTime() 159 { 160 return this._zeroTime; 161 }, 162 163 set zeroTime(x) 164 { 165 if (this._zeroTime === x) 166 return; 167 168 this._zeroTime = x || 0; 169 170 this._needsLayout(); 171 }, 172 173 get startTime() 174 { 175 return this._startTime; 176 }, 177 178 set startTime(x) 179 { 180 if (this._startTime === x) 181 return; 182 183 this._startTime = x || 0; 184 185 if (!isNaN(this._duration)) 186 this._endTime = this._startTime + this._duration; 187 188 this._needsLayout(); 189 }, 190 191 get duration() 192 { 193 if (!isNaN(this._duration)) 194 return this._duration; 195 return this.endTime - this.startTime; 196 }, 197 198 set duration(x) 199 { 200 if (this._duration === x) 201 return; 202 203 this._duration = x || NaN; 204 205 if (!isNaN(this._duration)) { 206 this._endTime = this._startTime + this._duration; 207 this._endTimePinned = true; 208 } else 209 this._endTimePinned = false; 210 211 this._needsLayout(); 212 }, 213 214 get endTime() 215 { 216 if (!this._endTimePinned && this._scheduledLayoutUpdateIdentifier) 217 this._recalculate(); 218 return this._endTime; 219 }, 220 221 set endTime(x) 222 { 223 if (this._endTime === x) 224 return; 225 226 this._endTime = x || 0; 227 this._endTimePinned = true; 228 229 this._needsLayout(); 230 }, 231 232 get secondsPerPixel() 233 { 234 if (this._scheduledLayoutUpdateIdentifier) 235 this._recalculate(); 236 return this._secondsPerPixel; 237 }, 238 239 set secondsPerPixel(x) 240 { 241 if (this._secondsPerPixel === x) 242 return; 243 244 this._secondsPerPixel = x || 0; 245 this._endTimePinned = false; 246 this._currentSliceTime = 0; 247 248 this._needsLayout(); 249 }, 250 251 get selectionStartTime() 252 { 253 return this._selectionStartTime; 254 }, 255 256 set selectionStartTime(x) 257 { 258 if (this._selectionStartTime === x) 259 return; 260 261 this._selectionStartTime = x || 0; 262 this._timeRangeSelectionChanged = true; 263 264 this._needsSelectionLayout(); 265 }, 266 267 get selectionEndTime() 268 { 269 return this._selectionEndTime; 270 }, 271 272 set selectionEndTime(x) 273 { 274 if (this._selectionEndTime === x) 275 return; 276 277 this._selectionEndTime = x || 0; 278 this._timeRangeSelectionChanged = true; 279 280 this._needsSelectionLayout(); 281 }, 282 283 addMarker: function(marker) 284 { 285 console.assert(marker instanceof WebInspector.TimelineMarker); 286 287 if (this._markerElementMap.has(marker)) 288 return; 289 290 marker.addEventListener(WebInspector.TimelineMarker.Event.TimeChanged, this._timelineMarkerTimeChanged, this); 291 292 var markerElement = document.createElement("div"); 293 markerElement.classList.add(WebInspector.TimelineRuler.BaseMarkerElementStyleClassName); 294 markerElement.classList.add(marker.type); 295 296 this._markerElementMap.set(marker, markerElement); 297 298 this._needsMarkerLayout(); 299 }, 300 301 elementForMarker: function(marker) 302 { 303 return this._markerElementMap.get(marker) || null; 304 }, 305 306 updateLayout: function() 307 { 308 if (this._scheduledLayoutUpdateIdentifier) { 309 cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier); 310 delete this._scheduledLayoutUpdateIdentifier; 311 } 312 313 var visibleWidth = this._recalculate(); 314 if (visibleWidth <= 0) 315 return; 316 317 var duration = this.duration; 318 319 var pixelsPerSecond = visibleWidth / duration; 320 321 // Calculate a divider count based on the maximum allowed divider density. 322 var dividerCount = Math.round(visibleWidth / WebInspector.TimelineRuler.MinimumDividerSpacing); 323 324 if (this._endTimePinned || !this._currentSliceTime) { 325 // Calculate the slice time based on the rough divider count and the time span. 326 var sliceTime = duration / dividerCount; 327 328 // Snap the slice time to a nearest number (e.g. 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, etc.) 329 sliceTime = Math.pow(10, Math.ceil(Math.log(sliceTime) / Math.LN10)); 330 if (sliceTime * pixelsPerSecond >= 5 * WebInspector.TimelineRuler.MinimumDividerSpacing) 331 sliceTime = sliceTime / 5; 332 if (sliceTime * pixelsPerSecond >= 2 * WebInspector.TimelineRuler.MinimumDividerSpacing) 333 sliceTime = sliceTime / 2; 334 335 this._currentSliceTime = sliceTime; 336 } else { 337 // Reuse the last slice time since the time duration does not scale to fit when the end time isn't pinned. 338 var sliceTime = this._currentSliceTime; 339 } 340 341 var firstDividerTime = (Math.ceil((this._startTime - this._zeroTime) / sliceTime) * sliceTime) + this._zeroTime; 342 var lastDividerTime = this._endTime; 343 344 // Calculate the divider count now based on the final slice time. 345 dividerCount = Math.ceil((lastDividerTime - firstDividerTime) / sliceTime); 346 347 // Make an extra divider in case the last one is partially visible. 348 if (!this._endTimePinned) 349 ++dividerCount; 350 351 var markerDividers = this._markersElement.querySelectorAll("." + WebInspector.TimelineRuler.DividerElementStyleClassName); 352 353 var dividerElement = this._headerElement.firstChild; 354 355 for (var i = 0; i <= dividerCount; ++i) { 356 if (!dividerElement) { 357 dividerElement = document.createElement("div"); 358 dividerElement.className = WebInspector.TimelineRuler.DividerElementStyleClassName; 359 this._headerElement.appendChild(dividerElement); 360 361 var labelElement = document.createElement("div"); 362 labelElement.className = WebInspector.TimelineRuler.DividerLabelElementStyleClassName; 363 dividerElement._labelElement = labelElement; 364 dividerElement.appendChild(labelElement); 365 } 366 367 var markerDividerElement = markerDividers[i]; 368 if (!markerDividerElement) { 369 markerDividerElement = document.createElement("div"); 370 markerDividerElement.className = WebInspector.TimelineRuler.DividerElementStyleClassName; 371 this._markersElement.appendChild(markerDividerElement); 372 } 373 374 var dividerTime = firstDividerTime + (sliceTime * i); 375 376 var newLeftPosition = (dividerTime - this._startTime) / duration; 377 378 if (!this._allowsClippedLabels) { 379 // Don't allow dividers under 0% where they will be completely hidden. 380 if (newLeftPosition < 0) 381 continue; 382 383 // When over 100% it is time to stop making/updating dividers. 384 if (newLeftPosition > 1) 385 break; 386 387 // Don't allow the left-most divider spacing to be so tight it clips. 388 if ((newLeftPosition * visibleWidth) < WebInspector.TimelineRuler.MinimumLeftDividerSpacing) 389 continue; 390 } 391 392 this._updatePositionOfElement(dividerElement, newLeftPosition, visibleWidth); 393 this._updatePositionOfElement(markerDividerElement, newLeftPosition, visibleWidth); 394 395 dividerElement._labelElement.textContent = isNaN(dividerTime) ? "" : Number.secondsToString(dividerTime - this._zeroTime, true); 396 dividerElement = dividerElement.nextSibling; 397 } 398 399 // Remove extra dividers. 400 while (dividerElement) { 401 var nextDividerElement = dividerElement.nextSibling; 402 dividerElement.remove(); 403 dividerElement = nextDividerElement; 404 } 405 406 for (; i < markerDividers.length; ++i) 407 markerDividers[i].remove(); 408 409 this._updateMarkers(visibleWidth, duration); 410 this._updateSelection(visibleWidth, duration); 411 }, 412 413 updateLayoutIfNeeded: function() 414 { 415 // If there is a main layout scheduled we can just update layout and return, since that 416 // will update markers and the selection at the same time. 417 if (this._scheduledLayoutUpdateIdentifier) { 418 this.updateLayout(); 419 return; 420 } 421 422 var visibleWidth = this._element.clientWidth; 423 if (visibleWidth <= 0) 424 return; 425 426 if (this._scheduledMarkerLayoutUpdateIdentifier) 427 this._updateMarkers(visibleWidth, this.duration); 428 429 if (this._scheduledSelectionLayoutUpdateIdentifier) 430 this._updateSelection(visibleWidth, this.duration); 431 }, 432 433 // Private 434 435 _needsLayout: function() 436 { 437 if (this._scheduledLayoutUpdateIdentifier) 438 return; 439 440 if (this._scheduledMarkerLayoutUpdateIdentifier) { 441 cancelAnimationFrame(this._scheduledMarkerLayoutUpdateIdentifier); 442 delete this._scheduledMarkerLayoutUpdateIdentifier; 443 } 444 445 if (this._scheduledSelectionLayoutUpdateIdentifier) { 446 cancelAnimationFrame(this._scheduledSelectionLayoutUpdateIdentifier); 447 delete this._scheduledSelectionLayoutUpdateIdentifier; 448 } 449 450 this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this)); 451 }, 452 453 _needsMarkerLayout: function() 454 { 455 // If layout is scheduled, abort since markers will be updated when layout happens. 456 if (this._scheduledLayoutUpdateIdentifier) 457 return; 458 459 if (this._scheduledMarkerLayoutUpdateIdentifier) 460 return; 461 462 function update() 463 { 464 delete this._scheduledMarkerLayoutUpdateIdentifier; 465 466 var visibleWidth = this._element.clientWidth; 467 if (visibleWidth <= 0) 468 return; 469 470 this._updateMarkers(visibleWidth, this.duration); 471 } 472 473 this._scheduledMarkerLayoutUpdateIdentifier = requestAnimationFrame(update.bind(this)); 474 }, 475 476 _needsSelectionLayout: function() 477 { 478 if (!this._allowsTimeRangeSelection) 479 return; 480 481 // If layout is scheduled, abort since the selection will be updated when layout happens. 482 if (this._scheduledLayoutUpdateIdentifier) 483 return; 484 485 if (this._scheduledSelectionLayoutUpdateIdentifier) 486 return; 487 488 function update() 489 { 490 delete this._scheduledSelectionLayoutUpdateIdentifier; 491 492 var visibleWidth = this._element.clientWidth; 493 if (visibleWidth <= 0) 494 return; 495 496 this._updateSelection(visibleWidth, this.duration); 497 } 498 499 this._scheduledSelectionLayoutUpdateIdentifier = requestAnimationFrame(update.bind(this)); 500 }, 501 502 _recalculate: function() 503 { 504 var visibleWidth = this._element.clientWidth; 505 if (visibleWidth <= 0) 506 return 0; 507 508 if (this._endTimePinned) 509 var duration = this._endTime - this._startTime; 510 else 511 var duration = visibleWidth * this._secondsPerPixel; 512 513 this._secondsPerPixel = duration / visibleWidth; 514 515 if (!this._endTimePinned) 516 this._endTime = this._startTime + (visibleWidth * this._secondsPerPixel); 517 518 return visibleWidth; 519 }, 520 521 _updatePositionOfElement: function(element, newPosition, visibleWidth, property) 522 { 523 property = property || "left"; 524 525 newPosition *= this._endTimePinned ? 100 : visibleWidth; 526 newPosition = newPosition.toFixed(2); 527 528 var currentPosition = parseFloat(element.style[property]).toFixed(2); 529 if (currentPosition !== newPosition) 530 element.style[property] = newPosition + (this._endTimePinned ? "%" : "px"); 531 }, 532 533 _updateMarkers: function(visibleWidth, duration) 534 { 535 if (this._scheduledMarkerLayoutUpdateIdentifier) { 536 cancelAnimationFrame(this._scheduledMarkerLayoutUpdateIdentifier); 537 delete this._scheduledMarkerLayoutUpdateIdentifier; 538 } 539 540 this._markerElementMap.forEach(function(markerElement, marker) { 541 var newLeftPosition = (marker.time - this._startTime) / duration; 542 543 this._updatePositionOfElement(markerElement, newLeftPosition, visibleWidth); 544 545 if (!markerElement.parentNode) 546 this._markersElement.appendChild(markerElement); 547 }, this); 548 }, 549 550 _updateSelection: function(visibleWidth, duration) 551 { 552 if (this._scheduledSelectionLayoutUpdateIdentifier) { 553 cancelAnimationFrame(this._scheduledSelectionLayoutUpdateIdentifier); 554 delete this._scheduledSelectionLayoutUpdateIdentifier; 555 } 556 557 this._element.classList.toggle(WebInspector.TimelineRuler.AllowsTimeRangeSelectionStyleClassName, this._allowsTimeRangeSelection); 558 559 if (!this._allowsTimeRangeSelection) 560 return; 561 562 var newLeftPosition = Math.max(0, (this._selectionStartTime - this._startTime) / duration); 563 this._updatePositionOfElement(this._leftShadedAreaElement, newLeftPosition, visibleWidth, "width"); 564 this._updatePositionOfElement(this._leftSelectionHandleElement, newLeftPosition, visibleWidth, "left"); 565 this._updatePositionOfElement(this._selectionDragElement, newLeftPosition, visibleWidth, "left"); 566 567 var newRightPosition = 1 - Math.min((this._selectionEndTime - this._startTime) / duration, 1); 568 this._updatePositionOfElement(this._rightShadedAreaElement, newRightPosition, visibleWidth, "width"); 569 this._updatePositionOfElement(this._rightSelectionHandleElement, newRightPosition, visibleWidth, "right"); 570 this._updatePositionOfElement(this._selectionDragElement, newRightPosition, visibleWidth, "right"); 571 572 if (!this._selectionDragElement.parentNode) { 573 this._element.appendChild(this._selectionDragElement); 574 this._element.appendChild(this._leftShadedAreaElement); 575 this._element.appendChild(this._leftSelectionHandleElement); 576 this._element.appendChild(this._rightShadedAreaElement); 577 this._element.appendChild(this._rightSelectionHandleElement); 578 } 579 580 if (this._timeRangeSelectionChanged) 581 this._dispatchTimeRangeSelectionChangedEvent(); 582 }, 583 584 _dispatchTimeRangeSelectionChangedEvent: function() 585 { 586 delete this._timeRangeSelectionChanged; 587 588 if (this._suppressTimeRangeSelectionChangedEvent) 589 return; 590 591 this.dispatchEventToListeners(WebInspector.TimelineRuler.Event.TimeRangeSelectionChanged); 592 }, 593 594 _timelineMarkerTimeChanged: function() 595 { 596 this._needsMarkerLayout(); 597 }, 598 599 _handleMouseDown: function(event) 600 { 601 // Only handle left mouse clicks. 602 if (event.button !== 0 || event.ctrlKey) 603 return; 604 605 this._selectionIsMove = event.target === this._selectionDragElement; 606 this._suppressTimeRangeSelectionChangedEvent = !this._selectionIsMove; 607 608 if (this._selectionIsMove) 609 this._lastMousePosition = event.pageX; 610 else 611 this._mouseDownPosition = event.pageX - this._element.totalOffsetLeft; 612 613 this._mouseMoveEventListener = this._handleMouseMove.bind(this); 614 this._mouseUpEventListener = this._handleMouseUp.bind(this); 615 616 // Register these listeners on the document so we can track the mouse if it leaves the ruler. 617 document.addEventListener("mousemove", this._mouseMoveEventListener); 618 document.addEventListener("mouseup", this._mouseUpEventListener); 619 620 event.preventDefault(); 621 event.stopPropagation(); 622 }, 623 624 _handleMouseMove: function(event) 625 { 626 console.assert(event.button === 0); 627 628 if (this._selectionIsMove) { 629 var currentMousePosition = event.pageX; 630 631 var offsetTime = (currentMousePosition - this._lastMousePosition) * this.secondsPerPixel; 632 var selectionDuration = this.selectionEndTime - this.selectionStartTime; 633 634 this.selectionStartTime = Math.max(this.startTime, Math.min(this.selectionStartTime + offsetTime, this.endTime - selectionDuration)); 635 this.selectionEndTime = this.selectionStartTime + selectionDuration; 636 637 this._lastMousePosition = currentMousePosition; 638 } else { 639 var currentMousePosition = event.pageX - this._element.totalOffsetLeft; 640 641 this.selectionStartTime = Math.max(this.startTime, this.startTime + (Math.min(currentMousePosition, this._mouseDownPosition) * this.secondsPerPixel)); 642 this.selectionEndTime = Math.min(this.startTime + (Math.max(currentMousePosition, this._mouseDownPosition) * this.secondsPerPixel), this.endTime); 643 } 644 645 this._updateSelection(this._element.clientWidth, this.duration); 646 647 event.preventDefault(); 648 event.stopPropagation(); 649 }, 650 651 _handleMouseUp: function(event) 652 { 653 console.assert(event.button === 0); 654 655 if (!this._selectionIsMove && this.selectionEndTime - this.selectionStartTime < WebInspector.TimelineRuler.MinimumSelectionTimeRange) { 656 // The section is smaller than allowed, grow in the direction of the drag to meet the minumum. 657 var currentMousePosition = event.pageX - this._element.totalOffsetLeft; 658 if (currentMousePosition > this._mouseDownPosition) { 659 this.selectionEndTime = Math.min(this.selectionStartTime + WebInspector.TimelineRuler.MinimumSelectionTimeRange, this.endTime); 660 this.selectionStartTime = this.selectionEndTime - WebInspector.TimelineRuler.MinimumSelectionTimeRange; 661 } else { 662 this.selectionStartTime = Math.max(this.startTime, this.selectionEndTime - WebInspector.TimelineRuler.MinimumSelectionTimeRange); 663 this.selectionEndTime = this.selectionStartTime + WebInspector.TimelineRuler.MinimumSelectionTimeRange 664 } 665 } 666 667 delete this._suppressTimeRangeSelectionChangedEvent; 668 669 this._dispatchTimeRangeSelectionChangedEvent(); 670 671 document.removeEventListener("mousemove", this._mouseMoveEventListener); 672 document.removeEventListener("mouseup", this._mouseUpEventListener); 673 674 delete this._mouseMovedEventListener; 675 delete this._mouseUpEventListener; 676 delete this._mouseDownPosition; 677 delete this._lastMousePosition; 678 delete this._selectionIsMove; 679 680 event.preventDefault(); 681 event.stopPropagation(); 682 }, 683 684 _handleSelectionHandleMouseDown: function(event) 685 { 686 // Only handle left mouse clicks. 687 if (event.button !== 0 || event.ctrlKey) 688 return; 689 690 this._dragHandleIsStartTime = event.target === this._leftSelectionHandleElement; 691 this._mouseDownPosition = event.pageX - this._element.totalOffsetLeft; 692 693 this._selectionHandleMouseMoveEventListener = this._handleSelectionHandleMouseMove.bind(this); 694 this._selectionHandleMouseUpEventListener = this._handleSelectionHandleMouseUp.bind(this); 695 696 // Register these listeners on the document so we can track the mouse if it leaves the ruler. 697 document.addEventListener("mousemove", this._selectionHandleMouseMoveEventListener); 698 document.addEventListener("mouseup", this._selectionHandleMouseUpEventListener); 699 700 event.preventDefault(); 701 event.stopPropagation(); 702 }, 703 704 _handleSelectionHandleMouseMove: function(event) 705 { 706 console.assert(event.button === 0); 707 708 var currentMousePosition = event.pageX - this._element.totalOffsetLeft; 709 var currentTime = this.startTime + (currentMousePosition * this.secondsPerPixel); 710 711 if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { 712 // Resize the selection on both sides when the Option keys is held down. 713 if (this._dragHandleIsStartTime) { 714 var timeDifference = currentTime - this.selectionStartTime; 715 this.selectionStartTime = Math.max(this.startTime, Math.min(currentTime, this.selectionEndTime - WebInspector.TimelineRuler.MinimumSelectionTimeRange)); 716 this.selectionEndTime = Math.min(Math.max(this.selectionStartTime + WebInspector.TimelineRuler.MinimumSelectionTimeRange, this.selectionEndTime - timeDifference), this.endTime); 717 } else { 718 var timeDifference = currentTime - this.selectionEndTime; 719 this.selectionEndTime = Math.min(Math.max(this.selectionStartTime + WebInspector.TimelineRuler.MinimumSelectionTimeRange, currentTime), this.endTime); 720 this.selectionStartTime = Math.max(this.startTime, Math.min(this.selectionStartTime - timeDifference, this.selectionEndTime - WebInspector.TimelineRuler.MinimumSelectionTimeRange)); 721 } 722 } else { 723 // Resize the selection on side being dragged. 724 if (this._dragHandleIsStartTime) 725 this.selectionStartTime = Math.max(this.startTime, Math.min(currentTime, this.selectionEndTime - WebInspector.TimelineRuler.MinimumSelectionTimeRange)); 726 else 727 this.selectionEndTime = Math.min(Math.max(this.selectionStartTime + WebInspector.TimelineRuler.MinimumSelectionTimeRange, currentTime), this.endTime); 728 } 729 730 this._updateSelection(this._element.clientWidth, this.duration); 731 732 event.preventDefault(); 733 event.stopPropagation(); 734 }, 735 736 _handleSelectionHandleMouseUp: function(event) 737 { 738 console.assert(event.button === 0); 739 740 document.removeEventListener("mousemove", this._selectionHandleMouseMoveEventListener); 741 document.removeEventListener("mouseup", this._selectionHandleMouseUpEventListener); 742 743 delete this._selectionHandleMouseMoveEventListener; 744 delete this._selectionHandleMouseUpEventListener; 745 delete this._dragHandleIsStartTime; 746 delete this._mouseDownPosition; 747 748 event.preventDefault(); 749 event.stopPropagation(); 750 } 751} 752 753WebInspector.TimelineRuler.prototype.__proto__ = WebInspector.Object.prototype; 754