1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// This module implements Webview (<webview>) as a custom element that wraps a 6// BrowserPlugin object element. The object element is hidden within 7// the shadow DOM of the Webview element. 8 9var DocumentNatives = requireNative('document_natives'); 10var GuestViewInternal = 11 require('binding').Binding.create('guestViewInternal').generate(); 12var IdGenerator = requireNative('id_generator'); 13var WebView = require('webview').WebView; 14var WebViewEvents = require('webViewEvents').WebViewEvents; 15 16var WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight'; 17var WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth'; 18var WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight'; 19var WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth'; 20var WEB_VIEW_ATTRIBUTE_PARTITION = 'partition'; 21 22var PLUGIN_METHOD_ATTACH = '-internal-attach'; 23 24var ERROR_MSG_ALREADY_NAVIGATED = 25 'The object has already navigated, so its partition cannot be changed.'; 26var ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.'; 27 28/** @type {Array.<string>} */ 29var WEB_VIEW_ATTRIBUTES = [ 30 'allowtransparency', 31 'autosize', 32 WEB_VIEW_ATTRIBUTE_MINHEIGHT, 33 WEB_VIEW_ATTRIBUTE_MINWIDTH, 34 WEB_VIEW_ATTRIBUTE_MAXHEIGHT, 35 WEB_VIEW_ATTRIBUTE_MAXWIDTH 36]; 37 38/** @class representing state of storage partition. */ 39function Partition() { 40 this.validPartitionId = true; 41 this.persistStorage = false; 42 this.storagePartitionId = ''; 43}; 44 45Partition.prototype.toAttribute = function() { 46 if (!this.validPartitionId) { 47 return ''; 48 } 49 return (this.persistStorage ? 'persist:' : '') + this.storagePartitionId; 50}; 51 52Partition.prototype.fromAttribute = function(value, hasNavigated) { 53 var result = {}; 54 if (hasNavigated) { 55 result.error = ERROR_MSG_ALREADY_NAVIGATED; 56 return result; 57 } 58 if (!value) { 59 value = ''; 60 } 61 62 var LEN = 'persist:'.length; 63 if (value.substr(0, LEN) == 'persist:') { 64 value = value.substr(LEN); 65 if (!value) { 66 this.validPartitionId = false; 67 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE; 68 return result; 69 } 70 this.persistStorage = true; 71 } else { 72 this.persistStorage = false; 73 } 74 75 this.storagePartitionId = value; 76 return result; 77}; 78 79// Implemented when the experimental API is available. 80WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {} 81 82/** 83 * @constructor 84 */ 85function WebViewInternal(webviewNode) { 86 privates(webviewNode).internal = this; 87 this.webviewNode = webviewNode; 88 this.attached = false; 89 90 this.beforeFirstNavigation = true; 91 this.validPartitionId = true; 92 // Used to save some state upon deferred attachment. 93 // If <object> bindings is not available, we defer attachment. 94 // This state contains whether or not the attachment request was for 95 // newwindow. 96 this.deferredAttachState = null; 97 98 // on* Event handlers. 99 this.on = {}; 100 101 this.browserPluginNode = this.createBrowserPluginNode(); 102 var shadowRoot = this.webviewNode.createShadowRoot(); 103 shadowRoot.appendChild(this.browserPluginNode); 104 105 this.setupWebviewNodeAttributes(); 106 this.setupFocusPropagation(); 107 this.setupWebviewNodeProperties(); 108 109 this.viewInstanceId = IdGenerator.GetNextId(); 110 111 this.partition = new Partition(); 112 this.parseAttributes(); 113 114 new WebViewEvents(this, this.viewInstanceId); 115} 116 117/** 118 * @private 119 */ 120WebViewInternal.prototype.createBrowserPluginNode = function() { 121 // We create BrowserPlugin as a custom element in order to observe changes 122 // to attributes synchronously. 123 var browserPluginNode = new WebViewInternal.BrowserPlugin(); 124 privates(browserPluginNode).internal = this; 125 126 $Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) { 127 // Only copy attributes that have been assigned values, rather than copying 128 // a series of undefined attributes to BrowserPlugin. 129 if (this.webviewNode.hasAttribute(attributeName)) { 130 browserPluginNode.setAttribute( 131 attributeName, this.webviewNode.getAttribute(attributeName)); 132 } else if (this.webviewNode[attributeName]){ 133 // Reading property using has/getAttribute does not work on 134 // document.DOMContentLoaded event (but works on 135 // window.DOMContentLoaded event). 136 // So copy from property if copying from attribute fails. 137 browserPluginNode.setAttribute( 138 attributeName, this.webviewNode[attributeName]); 139 } 140 }, this); 141 142 return browserPluginNode; 143}; 144 145WebViewInternal.prototype.getInstanceId = function() { 146 return this.instanceId; 147}; 148 149/** 150 * Resets some state upon reattaching <webview> element to the DOM. 151 */ 152WebViewInternal.prototype.resetUponReattachment = function() { 153 this.instanceId = undefined; 154 this.beforeFirstNavigation = true; 155 this.validPartitionId = true; 156 this.partition.validPartitionId = true; 157}; 158 159// Sets <webview>.request property. 160WebViewInternal.prototype.setRequestPropertyOnWebViewNode = function(request) { 161 Object.defineProperty( 162 this.webviewNode, 163 'request', 164 { 165 value: request, 166 enumerable: true 167 } 168 ); 169}; 170 171WebViewInternal.prototype.setupFocusPropagation = function() { 172 if (!this.webviewNode.hasAttribute('tabIndex')) { 173 // <webview> needs a tabIndex in order to be focusable. 174 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute 175 // to allow <webview> to be focusable. 176 // See http://crbug.com/231664. 177 this.webviewNode.setAttribute('tabIndex', -1); 178 } 179 var self = this; 180 this.webviewNode.addEventListener('focus', function(e) { 181 // Focus the BrowserPlugin when the <webview> takes focus. 182 self.browserPluginNode.focus(); 183 }); 184 this.webviewNode.addEventListener('blur', function(e) { 185 // Blur the BrowserPlugin when the <webview> loses focus. 186 self.browserPluginNode.blur(); 187 }); 188}; 189 190/** 191 * @private 192 */ 193WebViewInternal.prototype.back = function() { 194 return this.go(-1); 195}; 196 197/** 198 * @private 199 */ 200WebViewInternal.prototype.forward = function() { 201 return this.go(1); 202}; 203 204/** 205 * @private 206 */ 207WebViewInternal.prototype.canGoBack = function() { 208 return this.entryCount > 1 && this.currentEntryIndex > 0; 209}; 210 211/** 212 * @private 213 */ 214WebViewInternal.prototype.canGoForward = function() { 215 return this.currentEntryIndex >= 0 && 216 this.currentEntryIndex < (this.entryCount - 1); 217}; 218 219/** 220 * @private 221 */ 222WebViewInternal.prototype.clearData = function() { 223 if (!this.instanceId) { 224 return; 225 } 226 var args = $Array.concat([this.instanceId], $Array.slice(arguments)); 227 $Function.apply(WebView.clearData, null, args); 228}; 229 230/** 231 * @private 232 */ 233WebViewInternal.prototype.getProcessId = function() { 234 return this.processId; 235}; 236 237/** 238 * @private 239 */ 240WebViewInternal.prototype.go = function(relativeIndex) { 241 if (!this.instanceId) { 242 return; 243 } 244 WebView.go(this.instanceId, relativeIndex); 245}; 246 247/** 248 * @private 249 */ 250WebViewInternal.prototype.reload = function() { 251 if (!this.instanceId) { 252 return; 253 } 254 WebView.reload(this.instanceId); 255}; 256 257/** 258 * @private 259 */ 260WebViewInternal.prototype.stop = function() { 261 if (!this.instanceId) { 262 return; 263 } 264 WebView.stop(this.instanceId); 265}; 266 267/** 268 * @private 269 */ 270WebViewInternal.prototype.terminate = function() { 271 if (!this.instanceId) { 272 return; 273 } 274 WebView.terminate(this.instanceId); 275}; 276 277/** 278 * @private 279 */ 280WebViewInternal.prototype.validateExecuteCodeCall = function() { 281 var ERROR_MSG_CANNOT_INJECT_SCRIPT = '<webview>: ' + 282 'Script cannot be injected into content until the page has loaded.'; 283 if (!this.instanceId) { 284 throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT); 285 } 286}; 287 288/** 289 * @private 290 */ 291WebViewInternal.prototype.executeScript = function(var_args) { 292 this.validateExecuteCodeCall(); 293 var args = $Array.concat([this.instanceId, this.src], 294 $Array.slice(arguments)); 295 $Function.apply(WebView.executeScript, null, args); 296}; 297 298/** 299 * @private 300 */ 301WebViewInternal.prototype.insertCSS = function(var_args) { 302 this.validateExecuteCodeCall(); 303 var args = $Array.concat([this.instanceId, this.src], 304 $Array.slice(arguments)); 305 $Function.apply(WebView.insertCSS, null, args); 306}; 307 308/** 309 * @private 310 */ 311WebViewInternal.prototype.setupWebviewNodeProperties = function() { 312 var ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE = '<webview>: ' + 313 'contentWindow is not available at this time. It will become available ' + 314 'when the page has finished loading.'; 315 316 var self = this; 317 var browserPluginNode = this.browserPluginNode; 318 // Expose getters and setters for the attributes. 319 $Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) { 320 Object.defineProperty(this.webviewNode, attributeName, { 321 get: function() { 322 if (browserPluginNode.hasOwnProperty(attributeName)) { 323 return browserPluginNode[attributeName]; 324 } else { 325 return browserPluginNode.getAttribute(attributeName); 326 } 327 }, 328 set: function(value) { 329 if (browserPluginNode.hasOwnProperty(attributeName)) { 330 // Give the BrowserPlugin first stab at the attribute so that it can 331 // throw an exception if there is a problem. This attribute will then 332 // be propagated back to the <webview>. 333 browserPluginNode[attributeName] = value; 334 } else { 335 browserPluginNode.setAttribute(attributeName, value); 336 } 337 }, 338 enumerable: true 339 }); 340 }, this); 341 342 // <webview> src does not quite behave the same as BrowserPlugin src, and so 343 // we don't simply keep the two in sync. 344 this.src = this.webviewNode.getAttribute('src'); 345 Object.defineProperty(this.webviewNode, 'src', { 346 get: function() { 347 return self.src; 348 }, 349 set: function(value) { 350 self.webviewNode.setAttribute('src', value); 351 }, 352 // No setter. 353 enumerable: true 354 }); 355 356 Object.defineProperty(this.webviewNode, 'name', { 357 get: function() { 358 return self.name; 359 }, 360 set: function(value) { 361 self.webviewNode.setAttribute('name', value); 362 }, 363 enumerable: true 364 }); 365 366 Object.defineProperty(this.webviewNode, 'partition', { 367 get: function() { 368 return self.partition.toAttribute(); 369 }, 370 set: function(value) { 371 var result = self.partition.fromAttribute(value, self.hasNavigated()); 372 if (result.error) { 373 throw result.error; 374 } 375 self.webviewNode.setAttribute('partition', value); 376 }, 377 enumerable: true 378 }); 379 380 // We cannot use {writable: true} property descriptor because we want a 381 // dynamic getter value. 382 Object.defineProperty(this.webviewNode, 'contentWindow', { 383 get: function() { 384 if (browserPluginNode.contentWindow) 385 return browserPluginNode.contentWindow; 386 window.console.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE); 387 }, 388 // No setter. 389 enumerable: true 390 }); 391}; 392 393/** 394 * @private 395 */ 396WebViewInternal.prototype.setupWebviewNodeAttributes = function() { 397 this.setupWebViewSrcAttributeMutationObserver(); 398}; 399 400/** 401 * @private 402 */ 403WebViewInternal.prototype.setupWebViewSrcAttributeMutationObserver = 404 function() { 405 // The purpose of this mutation observer is to catch assignment to the src 406 // attribute without any changes to its value. This is useful in the case 407 // where the webview guest has crashed and navigating to the same address 408 // spawns off a new process. 409 var self = this; 410 this.srcAndPartitionObserver = new MutationObserver(function(mutations) { 411 $Array.forEach(mutations, function(mutation) { 412 var oldValue = mutation.oldValue; 413 var newValue = self.webviewNode.getAttribute(mutation.attributeName); 414 if (oldValue != newValue) { 415 return; 416 } 417 self.handleWebviewAttributeMutation( 418 mutation.attributeName, oldValue, newValue); 419 }); 420 }); 421 var params = { 422 attributes: true, 423 attributeOldValue: true, 424 attributeFilter: ['src', 'partition'] 425 }; 426 this.srcAndPartitionObserver.observe(this.webviewNode, params); 427}; 428 429/** 430 * @private 431 */ 432WebViewInternal.prototype.handleWebviewAttributeMutation = 433 function(name, oldValue, newValue) { 434 // This observer monitors mutations to attributes of the <webview> and 435 // updates the BrowserPlugin properties accordingly. In turn, updating 436 // a BrowserPlugin property will update the corresponding BrowserPlugin 437 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more 438 // details. 439 if (name == 'name') { 440 // We treat null attribute (attribute removed) and the empty string as 441 // one case. 442 oldValue = oldValue || ''; 443 newValue = newValue || ''; 444 445 if (oldValue === newValue) { 446 return; 447 } 448 this.name = newValue; 449 if (!this.instanceId) { 450 return; 451 } 452 WebView.setName(this.instanceId, newValue); 453 return; 454 } else if (name == 'src') { 455 // We treat null attribute (attribute removed) and the empty string as 456 // one case. 457 oldValue = oldValue || ''; 458 newValue = newValue || ''; 459 // Once we have navigated, we don't allow clearing the src attribute. 460 // Once <webview> enters a navigated state, it cannot be return back to a 461 // placeholder state. 462 if (newValue == '' && oldValue != '') { 463 // src attribute changes normally initiate a navigation. We suppress 464 // the next src attribute handler call to avoid reloading the page 465 // on every guest-initiated navigation. 466 this.ignoreNextSrcAttributeChange = true; 467 this.webviewNode.setAttribute('src', oldValue); 468 return; 469 } 470 this.src = newValue; 471 if (this.ignoreNextSrcAttributeChange) { 472 // Don't allow the src mutation observer to see this change. 473 this.srcAndPartitionObserver.takeRecords(); 474 this.ignoreNextSrcAttributeChange = false; 475 return; 476 } 477 var result = {}; 478 this.parseSrcAttribute(result); 479 480 if (result.error) { 481 throw result.error; 482 } 483 } else if (name == 'partition') { 484 // Note that throwing error here won't synchronously propagate. 485 this.partition.fromAttribute(newValue, this.hasNavigated()); 486 } 487 488 // No <webview> -> <object> mutation propagation for these attributes. 489 if (name == 'src' || name == 'partition') { 490 return; 491 } 492 493 if (this.browserPluginNode.hasOwnProperty(name)) { 494 this.browserPluginNode[name] = newValue; 495 } else { 496 this.browserPluginNode.setAttribute(name, newValue); 497 } 498}; 499 500/** 501 * @private 502 */ 503WebViewInternal.prototype.handleBrowserPluginAttributeMutation = 504 function(name, oldValue, newValue) { 505 if (name == 'internalbindings' && !oldValue && newValue) { 506 this.browserPluginNode.removeAttribute('internalbindings'); 507 508 if (this.deferredAttachState) { 509 var self = this; 510 // A setTimeout is necessary for the binding to be initialized properly. 511 window.setTimeout(function() { 512 if (self.hasBindings()) { 513 var params = self.buildAttachParams( 514 self.deferredAttachState.isNewWindow); 515 self.browserPluginNode[PLUGIN_METHOD_ATTACH](self.instanceId, params); 516 self.deferredAttachState = null; 517 } 518 }, 0); 519 } 520 return; 521 } 522 523 // This observer monitors mutations to attributes of the BrowserPlugin and 524 // updates the <webview> attributes accordingly. 525 // |newValue| is null if the attribute |name| has been removed. 526 if (newValue != null) { 527 // Update the <webview> attribute to match the BrowserPlugin attribute. 528 // Note: Calling setAttribute on <webview> will trigger its mutation 529 // observer which will then propagate that attribute to BrowserPlugin. In 530 // cases where we permit assigning a BrowserPlugin attribute the same value 531 // again (such as navigation when crashed), this could end up in an infinite 532 // loop. Thus, we avoid this loop by only updating the <webview> attribute 533 // if the BrowserPlugin attributes differs from it. 534 if (newValue != this.webviewNode.getAttribute(name)) { 535 this.webviewNode.setAttribute(name, newValue); 536 } 537 } else { 538 // If an attribute is removed from the BrowserPlugin, then remove it 539 // from the <webview> as well. 540 this.webviewNode.removeAttribute(name); 541 } 542}; 543 544WebViewInternal.prototype.onSizeChanged = function(newWidth, newHeight) { 545 var node = this.webviewNode; 546 547 var width = node.offsetWidth; 548 var height = node.offsetHeight; 549 550 // Check the current bounds to make sure we do not resize <webview> 551 // outside of current constraints. 552 var maxWidth; 553 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) && 554 node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]) { 555 maxWidth = node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]; 556 } else { 557 maxWidth = width; 558 } 559 560 var minWidth; 561 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH) && 562 node[WEB_VIEW_ATTRIBUTE_MINWIDTH]) { 563 minWidth = node[WEB_VIEW_ATTRIBUTE_MINWIDTH]; 564 } else { 565 minWidth = width; 566 } 567 if (minWidth > maxWidth) { 568 minWidth = maxWidth; 569 } 570 571 var maxHeight; 572 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT) && 573 node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]) { 574 maxHeight = node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]; 575 } else { 576 maxHeight = height; 577 } 578 var minHeight; 579 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) && 580 node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]) { 581 minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]; 582 } else { 583 minHeight = height; 584 } 585 if (minHeight > maxHeight) { 586 minHeight = maxHeight; 587 } 588 589 if (newWidth >= minWidth && 590 newWidth <= maxWidth && 591 newHeight >= minHeight && 592 newHeight <= maxHeight) { 593 node.style.width = newWidth + 'px'; 594 node.style.height = newHeight + 'px'; 595 } 596}; 597 598// Returns true if Browser Plugin bindings is available. 599// Bindings are unavailable if <object> is not in the render tree. 600WebViewInternal.prototype.hasBindings = function() { 601 return 'function' == typeof this.browserPluginNode[PLUGIN_METHOD_ATTACH]; 602}; 603 604WebViewInternal.prototype.hasNavigated = function() { 605 return !this.beforeFirstNavigation; 606}; 607 608/** @return {boolean} */ 609WebViewInternal.prototype.parseSrcAttribute = function(result) { 610 if (!this.partition.validPartitionId) { 611 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE; 612 return false; 613 } 614 this.src = this.webviewNode.getAttribute('src'); 615 616 if (!this.src) { 617 return true; 618 } 619 620 if (!this.hasGuestInstanceID()) { 621 if (this.beforeFirstNavigation) { 622 this.beforeFirstNavigation = false; 623 this.allocateInstanceId(); 624 } 625 return true; 626 } 627 628 // Navigate to this.src. 629 WebView.navigate(this.instanceId, this.src); 630 return true; 631}; 632 633/** @return {boolean} */ 634WebViewInternal.prototype.parseAttributes = function() { 635 var hasNavigated = this.hasNavigated(); 636 var attributeValue = this.webviewNode.getAttribute('partition'); 637 var result = this.partition.fromAttribute(attributeValue, hasNavigated); 638 return this.parseSrcAttribute(result); 639}; 640 641WebViewInternal.prototype.hasGuestInstanceID = function() { 642 return this.instanceId != undefined; 643}; 644 645WebViewInternal.prototype.allocateInstanceId = function() { 646 // Parse .src and .partition. 647 var self = this; 648 GuestViewInternal.allocateInstanceId( 649 function(instanceId) { 650 // TODO(lazyboy): Make sure this.autoNavigate_ stuff correctly updated 651 // |self.src| at this point. 652 self.attachWindow(instanceId, false); 653 }); 654}; 655 656WebViewInternal.prototype.onFrameNameChanged = function(name) { 657 this.name = name || ''; 658 if (this.name === '') { 659 this.webviewNode.removeAttribute('name'); 660 } else { 661 this.webviewNode.setAttribute('name', this.name); 662 } 663}; 664 665WebViewInternal.prototype.dispatchEvent = function(webViewEvent) { 666 return this.webviewNode.dispatchEvent(webViewEvent); 667}; 668 669/** 670 * Adds an 'on<event>' property on the webview, which can be used to set/unset 671 * an event handler. 672 */ 673WebViewInternal.prototype.setupEventProperty = function(eventName) { 674 var propertyName = 'on' + eventName.toLowerCase(); 675 var self = this; 676 var webviewNode = this.webviewNode; 677 Object.defineProperty(webviewNode, propertyName, { 678 get: function() { 679 return self.on[propertyName]; 680 }, 681 set: function(value) { 682 if (self.on[propertyName]) 683 webviewNode.removeEventListener(eventName, self.on[propertyName]); 684 self.on[propertyName] = value; 685 if (value) 686 webviewNode.addEventListener(eventName, value); 687 }, 688 enumerable: true 689 }); 690}; 691 692// Updates state upon loadcommit. 693WebViewInternal.prototype.onLoadCommit = function( 694 currentEntryIndex, entryCount, processId, url, isTopLevel) { 695 this.currentEntryIndex = currentEntryIndex; 696 this.entryCount = entryCount; 697 this.processId = processId; 698 var oldValue = this.webviewNode.getAttribute('src'); 699 var newValue = url; 700 if (isTopLevel && (oldValue != newValue)) { 701 // Touching the src attribute triggers a navigation. To avoid 702 // triggering a page reload on every guest-initiated navigation, 703 // we use the flag ignoreNextSrcAttributeChange here. 704 this.ignoreNextSrcAttributeChange = true; 705 this.webviewNode.setAttribute('src', newValue); 706 } 707}; 708 709WebViewInternal.prototype.onAttach = function(storagePartitionId) { 710 this.webviewNode.setAttribute('partition', storagePartitionId); 711 this.partition.fromAttribute(storagePartitionId, this.hasNavigated()); 712}; 713 714 715/** @private */ 716WebViewInternal.prototype.getUserAgent = function() { 717 return this.userAgentOverride || navigator.userAgent; 718}; 719 720/** @private */ 721WebViewInternal.prototype.isUserAgentOverridden = function() { 722 return !!this.userAgentOverride && 723 this.userAgentOverride != navigator.userAgent; 724}; 725 726/** @private */ 727WebViewInternal.prototype.setUserAgentOverride = function(userAgentOverride) { 728 this.userAgentOverride = userAgentOverride; 729 if (!this.instanceId) { 730 // If we are not attached yet, then we will pick up the user agent on 731 // attachment. 732 return; 733 } 734 WebView.overrideUserAgent(this.instanceId, userAgentOverride); 735}; 736 737WebViewInternal.prototype.buildAttachParams = function(isNewWindow) { 738 var params = { 739 'api': 'webview', 740 'instanceId': this.viewInstanceId, 741 'name': this.name, 742 // We don't need to navigate new window from here. 743 'src': isNewWindow ? undefined : this.src, 744 // If we have a partition from the opener, that will also be already 745 // set via this.onAttach(). 746 'storagePartitionId': this.partition.toAttribute(), 747 'userAgentOverride': this.userAgentOverride 748 }; 749 return params; 750}; 751 752WebViewInternal.prototype.attachWindow = function(instanceId, isNewWindow) { 753 this.instanceId = instanceId; 754 var params = this.buildAttachParams(isNewWindow); 755 756 if (!this.hasBindings()) { 757 // No bindings means that the plugin isn't there (display: none), we defer 758 // attachWindow in this case. 759 this.deferredAttachState = {isNewWindow: isNewWindow}; 760 return false; 761 } 762 763 this.deferredAttachState = null; 764 return this.browserPluginNode[PLUGIN_METHOD_ATTACH](this.instanceId, params); 765}; 766 767// Registers browser plugin <object> custom element. 768function registerBrowserPluginElement() { 769 var proto = Object.create(HTMLObjectElement.prototype); 770 771 proto.createdCallback = function() { 772 this.setAttribute('type', 'application/browser-plugin'); 773 // The <object> node fills in the <webview> container. 774 this.style.width = '100%'; 775 this.style.height = '100%'; 776 }; 777 778 proto.attributeChangedCallback = function(name, oldValue, newValue) { 779 var internal = privates(this).internal; 780 if (!internal) { 781 return; 782 } 783 internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue); 784 }; 785 786 proto.attachedCallback = function() { 787 // Load the plugin immediately. 788 var unused = this.nonExistentAttribute; 789 }; 790 791 WebViewInternal.BrowserPlugin = 792 DocumentNatives.RegisterElement('browser-plugin', {extends: 'object', 793 prototype: proto}); 794 795 delete proto.createdCallback; 796 delete proto.attachedCallback; 797 delete proto.detachedCallback; 798 delete proto.attributeChangedCallback; 799} 800 801// Registers <webview> custom element. 802function registerWebViewElement() { 803 var proto = Object.create(HTMLElement.prototype); 804 805 proto.createdCallback = function() { 806 new WebViewInternal(this); 807 }; 808 809 proto.customElementDetached = false; 810 811 proto.attributeChangedCallback = function(name, oldValue, newValue) { 812 var internal = privates(this).internal; 813 if (!internal) { 814 return; 815 } 816 internal.handleWebviewAttributeMutation(name, oldValue, newValue); 817 }; 818 819 proto.detachedCallback = function() { 820 this.customElementDetached = true; 821 }; 822 823 proto.attachedCallback = function() { 824 if (this.customElementDetached) { 825 var webViewInternal = privates(this).internal; 826 webViewInternal.resetUponReattachment(); 827 webViewInternal.allocateInstanceId(); 828 } 829 this.customElementDetached = false; 830 }; 831 832 var methods = [ 833 'back', 834 'forward', 835 'canGoBack', 836 'canGoForward', 837 'clearData', 838 'getProcessId', 839 'go', 840 'reload', 841 'stop', 842 'terminate', 843 'executeScript', 844 'insertCSS', 845 'getUserAgent', 846 'isUserAgentOverridden', 847 'setUserAgentOverride' 848 ]; 849 850 // Forward proto.foo* method calls to WebViewInternal.foo*. 851 for (var i = 0; methods[i]; ++i) { 852 var createHandler = function(m) { 853 return function(var_args) { 854 var internal = privates(this).internal; 855 return $Function.apply(internal[m], internal, arguments); 856 }; 857 }; 858 proto[methods[i]] = createHandler(methods[i]); 859 } 860 861 WebViewInternal.maybeRegisterExperimentalAPIs(proto); 862 863 window.WebView = 864 DocumentNatives.RegisterElement('webview', {prototype: proto}); 865 866 // Delete the callbacks so developers cannot call them and produce unexpected 867 // behavior. 868 delete proto.createdCallback; 869 delete proto.attachedCallback; 870 delete proto.detachedCallback; 871 delete proto.attributeChangedCallback; 872} 873 874var useCapture = true; 875window.addEventListener('readystatechange', function listener(event) { 876 if (document.readyState == 'loading') 877 return; 878 879 registerBrowserPluginElement(); 880 registerWebViewElement(); 881 window.removeEventListener(event.type, listener, useCapture); 882}, useCapture); 883 884/** 885 * Implemented when the experimental API is available. 886 * @private 887 */ 888WebViewInternal.prototype.maybeGetExperimentalEvents = function() {}; 889 890/** 891 * Implemented when the experimental API is available. 892 * @private 893 */ 894WebViewInternal.prototype.maybeGetExperimentalPermissions = function() { 895 return []; 896}; 897 898/** 899 * Calls to show contextmenu right away instead of dispatching a 'contextmenu' 900 * event. 901 * This will be overridden in web_view_experimental.js to implement contextmenu 902 * API. 903 */ 904WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) { 905 var requestId = e.requestId; 906 // Setting |params| = undefined will show the context menu unmodified, hence 907 // the 'contextmenu' API is disabled for stable channel. 908 var params = undefined; 909 WebView.showContextMenu(this.instanceId, requestId, params); 910}; 911 912/** 913 * Implemented when the experimental API is available. 914 * @private 915 */ 916WebViewInternal.prototype.setupExperimentalContextMenus = function() {}; 917 918exports.WebView = WebView; 919exports.WebViewInternal = WebViewInternal; 920