| /* |
| * 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. |
| */ |
| |
| WebInspector.ContentViewContainer = function(element) |
| { |
| // FIXME: Convert this to a WebInspector.Object subclass, and call super(). |
| // WebInspector.Object.call(this); |
| |
| this._element = element || document.createElement("div"); |
| this._element.classList.add(WebInspector.ContentViewContainer.StyleClassName); |
| |
| this._backForwardList = []; |
| this._currentIndex = -1; |
| }; |
| |
| WebInspector.ContentViewContainer.StyleClassName = "content-view-container"; |
| |
| WebInspector.ContentViewContainer.Event = { |
| CurrentContentViewDidChange: "content-view-container-current-content-view-did-change" |
| }; |
| |
| WebInspector.ContentViewContainer.prototype = { |
| constructor: WebInspector.ContentViewContainer, |
| |
| // Public |
| |
| get element() |
| { |
| return this._element; |
| }, |
| |
| get currentIndex() |
| { |
| return this._currentIndex; |
| }, |
| |
| get backForwardList() |
| { |
| return this._backForwardList; |
| }, |
| |
| get currentContentView() |
| { |
| if (this._currentIndex < 0 || this._currentIndex > this._backForwardList.length - 1) |
| return null; |
| return this._backForwardList[this._currentIndex].contentView; |
| }, |
| |
| get currentBackForwardEntry() |
| { |
| if (this._currentIndex < 0 || this._currentIndex > this._backForwardList.length - 1) |
| return null; |
| return this._backForwardList[this._currentIndex]; |
| }, |
| |
| updateLayout: function() |
| { |
| var currentContentView = this.currentContentView; |
| if (currentContentView) |
| currentContentView.updateLayout(); |
| }, |
| |
| contentViewForRepresentedObject: function(representedObject, onlyExisting) |
| { |
| console.assert(representedObject); |
| if (!representedObject) |
| return null; |
| |
| // Iterate over all the known content views for the representedObject (if any) and find one that doesn't |
| // have a parent container or has this container as its parent. |
| var contentView = null; |
| for (var i = 0; representedObject.__contentViews && i < representedObject.__contentViews.length; ++i) { |
| var currentContentView = representedObject.__contentViews[i]; |
| if (!currentContentView._parentContainer || currentContentView._parentContainer === this) { |
| contentView = currentContentView; |
| break; |
| } |
| } |
| |
| console.assert(!contentView || contentView instanceof WebInspector.ContentView); |
| if (contentView instanceof WebInspector.ContentView) |
| return contentView; |
| |
| // Return early to avoid creating a new content view when onlyExisting is true. |
| if (onlyExisting) |
| return null; |
| |
| try { |
| // No existing content view found, make a new one. |
| contentView = new WebInspector.ContentView(representedObject); |
| } catch (e) { |
| console.error(e); |
| return null; |
| } |
| |
| // Remember this content view for future calls. |
| if (!representedObject.__contentViews) |
| representedObject.__contentViews = []; |
| representedObject.__contentViews.push(contentView); |
| |
| return contentView; |
| }, |
| |
| showContentViewForRepresentedObject: function(representedObject) |
| { |
| var contentView = this.contentViewForRepresentedObject(representedObject); |
| if (!contentView) |
| return null; |
| |
| this.showContentView(contentView); |
| |
| return contentView; |
| }, |
| |
| showContentView: function(contentView, cookie) |
| { |
| console.assert(contentView instanceof WebInspector.ContentView); |
| if (!(contentView instanceof WebInspector.ContentView)) |
| return null; |
| |
| // Don't allow showing a content view that is already associated with another container. |
| // Showing a content view that is already associated with this container is allowed. |
| console.assert(!contentView.parentContainer || contentView.parentContainer === this); |
| if (contentView.parentContainer && contentView.parentContainer !== this) |
| return null; |
| |
| var currentEntry = this.currentBackForwardEntry; |
| var provisionalEntry = new WebInspector.BackForwardEntry(contentView, cookie); |
| // Don't do anything if we would have added an identical back/forward list entry. |
| if (currentEntry && currentEntry.contentView === contentView && Object.shallowEqual(provisionalEntry.cookie, currentEntry.cookie)) { |
| const shouldCallShown = false; |
| currentEntry.prepareToShow(shouldCallShown); |
| return currentEntry.contentView; |
| } |
| |
| // Showing a content view will truncate the back/forward list after the current index and insert the content view |
| // at the end of the list. Finally, the current index will be updated to point to the end of the back/forward list. |
| |
| // Increment the current index to where we will insert the content view. |
| var newIndex = this._currentIndex + 1; |
| |
| // Insert the content view at the new index. This will remove any content views greater than or equal to the index. |
| var removedEntries = this._backForwardList.splice(newIndex, this._backForwardList.length - newIndex, provisionalEntry); |
| |
| console.assert(newIndex === this._backForwardList.length - 1); |
| console.assert(this._backForwardList[newIndex] === provisionalEntry); |
| |
| // Disassociate with the removed content views. |
| for (var i = 0; i < removedEntries.length; ++i) { |
| // Skip disassociation if this content view is still in the back/forward list. |
| var shouldDissociateContentView = this._backForwardList.some(function(existingEntry) { |
| return existingEntry.contentView === removedEntries[i].contentView; |
| }); |
| if (shouldDissociateContentView) |
| this._disassociateFromContentView(removedEntries[i]); |
| } |
| |
| // Associate with the new content view. |
| contentView._parentContainer = this; |
| |
| this.showBackForwardEntryForIndex(newIndex); |
| |
| return contentView; |
| }, |
| |
| showBackForwardEntryForIndex: function(index) |
| { |
| console.assert(index >= 0 && index <= this._backForwardList.length - 1); |
| if (index < 0 || index > this._backForwardList.length - 1) |
| return; |
| |
| if (this._currentIndex === index) |
| return; |
| |
| var previousEntry = this.currentBackForwardEntry; |
| this._currentIndex = index; |
| var currentEntry = this.currentBackForwardEntry; |
| console.assert(currentEntry); |
| |
| var isNewContentView = !previousEntry || !currentEntry.contentView.visible; |
| if (isNewContentView) { |
| // Hide the currently visible content view. |
| if (previousEntry) |
| this._hideEntry(previousEntry); |
| this._showEntry(currentEntry, true); |
| } else |
| this._showEntry(currentEntry, false); |
| |
| this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); |
| }, |
| |
| replaceContentView: function(oldContentView, newContentView) |
| { |
| console.assert(oldContentView instanceof WebInspector.ContentView); |
| if (!(oldContentView instanceof WebInspector.ContentView)) |
| return; |
| |
| console.assert(newContentView instanceof WebInspector.ContentView); |
| if (!(newContentView instanceof WebInspector.ContentView)) |
| return; |
| |
| console.assert(oldContentView.parentContainer === this); |
| if (oldContentView.parentContainer !== this) |
| return; |
| |
| console.assert(!newContentView.parentContainer || newContentView.parentContainer === this); |
| if (newContentView.parentContainer && newContentView.parentContainer !== this) |
| return; |
| |
| var currentlyShowing = (this.currentContentView === oldContentView); |
| if (currentlyShowing) |
| this._hideEntry(this.currentBackForwardEntry); |
| |
| // Disassociate with the old content view. |
| this._disassociateFromContentView(oldContentView); |
| |
| // Associate with the new content view. |
| newContentView._parentContainer = this; |
| |
| // Replace all occurrences of oldContentView with newContentView in the back/forward list. |
| for (var i = 0; i < this._backForwardList.length; ++i) { |
| if (this._backForwardList[i].contentView === oldContentView) |
| this._backForwardList[i].contentView = newContentView; |
| } |
| |
| // Re-show the current entry, because its content view instance was replaced. |
| if (currentlyShowing) { |
| this._showEntry(this.currentBackForwardEntry, true); |
| this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); |
| } |
| }, |
| |
| closeAllContentViewsOfPrototype: function(constructor) |
| { |
| if (!this._backForwardList.length) { |
| console.assert(this._currentIndex === -1); |
| return; |
| } |
| |
| // Do a check to see if all the content views are instances of this prototype. |
| // If they all are we can use the quicker closeAllContentViews method. |
| var allSamePrototype = true; |
| for (var i = this._backForwardList.length - 1; i >= 0; --i) { |
| if (!(this._backForwardList[i].contentView instanceof constructor)) { |
| allSamePrototype = false; |
| break; |
| } |
| } |
| |
| if (allSamePrototype) { |
| this.closeAllContentViews(); |
| return; |
| } |
| |
| var oldCurrentContentView = this.currentContentView; |
| |
| var backForwardListDidChange = false; |
| // Hide and disassociate with all the content views that are instances of the constructor. |
| for (var i = this._backForwardList.length - 1; i >= 0; --i) { |
| var entry = this._backForwardList[i]; |
| if (!(entry.contentView instanceof constructor)) |
| continue; |
| |
| if (entry.contentView === oldCurrentContentView) |
| this._hideEntry(entry); |
| |
| if (this._currentIndex >= i) { |
| // Decrement the currentIndex since we will remove an item in the back/forward array |
| // that it the current index or comes before it. |
| --this._currentIndex; |
| } |
| |
| this._disassociateFromContentView(entry.contentView); |
| |
| // Remove the item from the back/forward list. |
| this._backForwardList.splice(i, 1); |
| backForwardListDidChange = true; |
| } |
| |
| var currentEntry = this.currentBackForwardEntry; |
| console.assert(currentEntry || (!currentEntry && this._currentIndex === -1)); |
| |
| if (currentEntry && currentEntry.contentView !== oldCurrentContentView || backForwardListDidChange) { |
| this._showEntry(currentEntry, true); |
| this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); |
| } |
| }, |
| |
| closeAllContentViews: function() |
| { |
| if (!this._backForwardList.length) { |
| console.assert(this._currentIndex === -1); |
| return; |
| } |
| |
| // Hide and disassociate with all the content views. |
| for (var i = 0; i < this._backForwardList.length; ++i) { |
| var entry = this._backForwardList[i]; |
| if (i === this._currentIndex) |
| this._hideEntry(entry); |
| this._disassociateFromContentView(entry.contentView); |
| } |
| |
| this._backForwardList = []; |
| this._currentIndex = -1; |
| |
| this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); |
| }, |
| |
| canGoBack: function() |
| { |
| return this._currentIndex > 0; |
| }, |
| |
| canGoForward: function() |
| { |
| return this._currentIndex < this._backForwardList.length - 1; |
| }, |
| |
| goBack: function() |
| { |
| if (!this.canGoBack()) |
| return; |
| this.showBackForwardEntryForIndex(this._currentIndex - 1); |
| }, |
| |
| goForward: function() |
| { |
| if (!this.canGoForward()) |
| return; |
| this.showBackForwardEntryForIndex(this._currentIndex + 1); |
| }, |
| |
| shown: function() |
| { |
| var currentEntry = this.currentBackForwardEntry; |
| if (!currentEntry) |
| return; |
| |
| this._showEntry(currentEntry, true); |
| }, |
| |
| hidden: function() |
| { |
| var currentEntry = this.currentBackForwardEntry; |
| if (!currentEntry) |
| return; |
| |
| this._hideEntry(currentEntry); |
| }, |
| |
| // Private |
| |
| _addContentViewElement: function(contentView) |
| { |
| if (contentView.element.parentNode !== this._element) |
| this._element.appendChild(contentView.element); |
| }, |
| |
| _removeContentViewElement: function(contentView) |
| { |
| if (contentView.element.parentNode) |
| contentView.element.parentNode.removeChild(contentView.element); |
| }, |
| |
| _disassociateFromContentView: function(contentView) |
| { |
| console.assert(!contentView.visible); |
| |
| contentView._parentContainer = null; |
| |
| var representedObject = contentView.representedObject; |
| if (!representedObject || !representedObject.__contentViews) |
| return; |
| |
| representedObject.__contentViews.remove(contentView); |
| |
| contentView.closed(); |
| }, |
| |
| _showEntry: function(entry, shouldCallShown) |
| { |
| console.assert(entry instanceof WebInspector.BackForwardEntry); |
| |
| this._addContentViewElement(entry.contentView); |
| entry.prepareToShow(shouldCallShown); |
| }, |
| |
| _hideEntry: function(entry) |
| { |
| console.assert(entry instanceof WebInspector.BackForwardEntry); |
| |
| entry.prepareToHide(); |
| this._removeContentViewElement(entry.contentView); |
| } |
| }; |
| |
| WebInspector.ContentViewContainer.prototype.__proto__ = WebInspector.Object.prototype; |