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

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

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

Powered by Google App Engine
This is Rietveld 408576698