Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(528)

Side by Side Diff: chrome/renderer/resources/extensions/web_view.js

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

Powered by Google App Engine
This is Rietveld 408576698