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.ContentViewContainer = function(element) 27{ 28 WebInspector.Object.call(this); 29 30 this._element = element || document.createElement("div"); 31 this._element.classList.add(WebInspector.ContentViewContainer.StyleClassName); 32 33 this._backForwardList = []; 34 this._currentIndex = -1; 35}; 36 37WebInspector.ContentViewContainer.StyleClassName = "content-view-container"; 38 39WebInspector.ContentViewContainer.Event = { 40 CurrentContentViewDidChange: "content-view-container-current-content-view-did-change" 41}; 42 43WebInspector.ContentViewContainer.prototype = { 44 constructor: WebInspector.ContentViewContainer, 45 46 // Public 47 48 get element() 49 { 50 return this._element; 51 }, 52 53 get currentIndex() 54 { 55 return this._currentIndex; 56 }, 57 58 get backForwardList() 59 { 60 return this._backForwardList; 61 }, 62 63 get currentContentView() 64 { 65 if (this._currentIndex < 0 || this._currentIndex > this._backForwardList.length - 1) 66 return null; 67 return this._backForwardList[this._currentIndex].contentView; 68 }, 69 70 get currentBackForwardEntry() 71 { 72 if (this._currentIndex < 0 || this._currentIndex > this._backForwardList.length - 1) 73 return null; 74 return this._backForwardList[this._currentIndex]; 75 }, 76 77 updateLayout: function() 78 { 79 var currentContentView = this.currentContentView; 80 if (currentContentView) 81 currentContentView.updateLayout(); 82 }, 83 84 contentViewForRepresentedObject: function(representedObject, onlyExisting) 85 { 86 console.assert(representedObject); 87 if (!representedObject) 88 return null; 89 90 // Iterate over all the known content views for the representedObject (if any) and find one that doesn't 91 // have a parent container or has this container as its parent. 92 var contentView = null; 93 for (var i = 0; representedObject.__contentViews && i < representedObject.__contentViews.length; ++i) { 94 var currentContentView = representedObject.__contentViews[i]; 95 if (!currentContentView._parentContainer || currentContentView._parentContainer === this) { 96 contentView = currentContentView; 97 break; 98 } 99 } 100 101 console.assert(!contentView || contentView instanceof WebInspector.ContentView); 102 if (contentView instanceof WebInspector.ContentView) 103 return contentView; 104 105 // Return early to avoid creating a new content view when onlyExisting is true. 106 if (onlyExisting) 107 return null; 108 109 try { 110 // No existing content view found, make a new one. 111 contentView = new WebInspector.ContentView(representedObject); 112 } catch (e) { 113 console.error(e); 114 return null; 115 } 116 117 // Remember this content view for future calls. 118 if (!representedObject.__contentViews) 119 representedObject.__contentViews = []; 120 representedObject.__contentViews.push(contentView); 121 122 return contentView; 123 }, 124 125 showContentViewForRepresentedObject: function(representedObject) 126 { 127 var contentView = this.contentViewForRepresentedObject(representedObject); 128 if (!contentView) 129 return null; 130 131 this.showContentView(contentView); 132 133 return contentView; 134 }, 135 136 showContentView: function(contentView, cookie) 137 { 138 console.assert(contentView instanceof WebInspector.ContentView); 139 if (!(contentView instanceof WebInspector.ContentView)) 140 return null; 141 142 // Don't allow showing a content view that is already associated with another container. 143 // Showing a content view that is already associated with this container is allowed. 144 console.assert(!contentView.parentContainer || contentView.parentContainer === this); 145 if (contentView.parentContainer && contentView.parentContainer !== this) 146 return null; 147 148 var currentEntry = this.currentBackForwardEntry; 149 var provisionalEntry = new WebInspector.BackForwardEntry(contentView, cookie); 150 // Don't do anything if we would have added an identical back/forward list entry. 151 if (currentEntry && currentEntry.contentView === contentView && Object.shallowEqual(provisionalEntry.cookie, currentEntry.cookie)) 152 return currentEntry.contentView; 153 154 // Showing a content view will truncate the back/forward list after the current index and insert the content view 155 // at the end of the list. Finally, the current index will be updated to point to the end of the back/forward list. 156 157 // Increment the current index to where we will insert the content view. 158 var newIndex = this._currentIndex + 1; 159 160 // Insert the content view at the new index. This will remove any content views greater than or equal to the index. 161 var removedEntries = this._backForwardList.splice(newIndex, this._backForwardList.length - newIndex, provisionalEntry); 162 163 console.assert(newIndex === this._backForwardList.length - 1); 164 console.assert(this._backForwardList[newIndex] === provisionalEntry); 165 166 // Disassociate with the removed content views. 167 for (var i = 0; i < removedEntries.length; ++i) { 168 // Skip disassociation if this content view is still in the back/forward list. 169 var shouldDissociateContentView = this._backForwardList.some(function(existingEntry) { 170 return existingEntry.contentView === removedEntries[i].contentView; 171 }); 172 if (shouldDissociateContentView) 173 this._disassociateFromContentView(removedEntries[i]); 174 } 175 176 // Associate with the new content view. 177 contentView._parentContainer = this; 178 179 this.showBackForwardEntryForIndex(newIndex); 180 181 return contentView; 182 }, 183 184 showBackForwardEntryForIndex: function(index) 185 { 186 console.assert(index >= 0 && index <= this._backForwardList.length - 1); 187 if (index < 0 || index > this._backForwardList.length - 1) 188 return; 189 190 if (this._currentIndex === index) 191 return; 192 193 // Hide the currently visible content view. 194 var previousEntry = this.currentBackForwardEntry; 195 if (previousEntry) 196 this._hideEntry(previousEntry); 197 198 this._currentIndex = index; 199 var currentEntry = this.currentBackForwardEntry; 200 console.assert(currentEntry); 201 202 this._showEntry(currentEntry); 203 204 this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); 205 }, 206 207 replaceContentView: function(oldContentView, newContentView) 208 { 209 console.assert(oldContentView instanceof WebInspector.ContentView); 210 if (!(oldContentView instanceof WebInspector.ContentView)) 211 return; 212 213 console.assert(newContentView instanceof WebInspector.ContentView); 214 if (!(newContentView instanceof WebInspector.ContentView)) 215 return; 216 217 console.assert(oldContentView.parentContainer === this); 218 if (oldContentView.parentContainer !== this) 219 return; 220 221 console.assert(!newContentView.parentContainer || newContentView.parentContainer === this); 222 if (newContentView.parentContainer && newContentView.parentContainer !== this) 223 return; 224 225 var currentlyShowing = (this.currentContentView === oldContentView); 226 if (currentlyShowing) 227 this._hideEntry(this.currentBackForwardEntry); 228 229 // Disassociate with the old content view. 230 this._disassociateFromContentView(oldContentView); 231 232 // Associate with the new content view. 233 newContentView._parentContainer = this; 234 235 // Replace all occurrences of oldContentView with newContentView in the back/forward list. 236 for (var i = 0; i < this._backForwardList.length; ++i) { 237 if (this._backForwardList[i].contentView === oldContentView) 238 this._backForwardList[i].contentView = newContentView; 239 } 240 241 // Re-show the current entry, because its content view instance was replaced. 242 if (currentlyShowing) { 243 this._showEntry(this.currentBackForwardEntry); 244 this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); 245 } 246 }, 247 248 closeAllContentViewsOfPrototype: function(constructor) 249 { 250 if (!this._backForwardList.length) { 251 console.assert(this._currentIndex === -1); 252 return; 253 } 254 255 // Do a check to see if all the content views are instances of this prototype. 256 // If they all are we can use the quicker closeAllContentViews method. 257 var allSamePrototype = true; 258 for (var i = this._backForwardList.length - 1; i >= 0; --i) { 259 if (!(this._backForwardList[i].contentView instanceof constructor)) { 260 allSamePrototype = false; 261 break; 262 } 263 } 264 265 if (allSamePrototype) { 266 this.closeAllContentViews(); 267 return; 268 } 269 270 var oldCurrentContentView = this.currentContentView; 271 272 var backForwardListDidChange = false; 273 // Hide and disassociate with all the content views that are instances of the constructor. 274 for (var i = this._backForwardList.length - 1; i >= 0; --i) { 275 var entry = this._backForwardList[i]; 276 if (!(entry.contentView instanceof constructor)) 277 continue; 278 279 if (entry.contentView === oldCurrentContentView) 280 this._hideEntry(entry); 281 282 if (this._currentIndex >= i) { 283 // Decrement the currentIndex since we will remove an item in the back/forward array 284 // that it the current index or comes before it. 285 --this._currentIndex; 286 } 287 288 this._disassociateFromContentView(entry.contentView); 289 290 // Remove the item from the back/forward list. 291 this._backForwardList.splice(i, 1); 292 backForwardListDidChange = true; 293 } 294 295 var currentEntry = this.currentBackForwardEntry; 296 console.assert(currentEntry || (!currentEntry && this._currentIndex === -1)); 297 298 if (currentEntry && currentEntry.contentView !== oldCurrentContentView || backForwardListDidChange) { 299 this._showEntry(currentEntry); 300 this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); 301 } 302 }, 303 304 closeAllContentViews: function() 305 { 306 if (!this._backForwardList.length) { 307 console.assert(this._currentIndex === -1); 308 return; 309 } 310 311 // Hide and disassociate with all the content views. 312 for (var i = 0; i < this._backForwardList.length; ++i) { 313 var entry = this._backForwardList[i]; 314 if (i === this._currentIndex) 315 this._hideEntry(entry); 316 this._disassociateFromContentView(entry.contentView); 317 } 318 319 this._backForwardList = []; 320 this._currentIndex = -1; 321 322 this.dispatchEventToListeners(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange); 323 }, 324 325 canGoBack: function() 326 { 327 return this._currentIndex > 0; 328 }, 329 330 canGoForward: function() 331 { 332 return this._currentIndex < this._backForwardList.length - 1; 333 }, 334 335 goBack: function() 336 { 337 if (!this.canGoBack()) 338 return; 339 this.showBackForwardEntryForIndex(this._currentIndex - 1); 340 }, 341 342 goForward: function() 343 { 344 if (!this.canGoForward()) 345 return; 346 this.showBackForwardEntryForIndex(this._currentIndex + 1); 347 }, 348 349 shown: function() 350 { 351 var currentEntry = this.currentBackForwardEntry; 352 if (!currentEntry) 353 return; 354 355 this._showEntry(currentEntry); 356 }, 357 358 hidden: function() 359 { 360 var currentEntry = this.currentBackForwardEntry; 361 if (!currentEntry) 362 return; 363 364 this._hideEntry(currentEntry); 365 }, 366 367 // Private 368 369 _addContentViewElement: function(contentView) 370 { 371 if (contentView.element.parentNode !== this._element) 372 this._element.appendChild(contentView.element); 373 }, 374 375 _removeContentViewElement: function(contentView) 376 { 377 if (contentView.element.parentNode) 378 contentView.element.parentNode.removeChild(contentView.element); 379 }, 380 381 _disassociateFromContentView: function(contentView) 382 { 383 console.assert(!contentView.visible); 384 385 contentView._parentContainer = null; 386 387 var representedObject = contentView.representedObject; 388 if (!representedObject || !representedObject.__contentViews) 389 return; 390 391 representedObject.__contentViews.remove(contentView); 392 393 contentView.closed(); 394 }, 395 396 _showEntry: function(entry) 397 { 398 console.assert(entry instanceof WebInspector.BackForwardEntry); 399 400 this._addContentViewElement(entry.contentView); 401 entry.prepareToShow(); 402 }, 403 404 _hideEntry: function(entry) 405 { 406 console.assert(entry instanceof WebInspector.BackForwardEntry); 407 408 entry.prepareToHide(); 409 this._removeContentViewElement(entry.contentView); 410 } 411}; 412 413WebInspector.ContentViewContainer.prototype.__proto__ = WebInspector.Object.prototype; 414