Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 <?xml version="1.0"?>
3 <!-- This Source Code Form is subject to the terms of the Mozilla Public
4 - License, v. 2.0. If a copy of the MPL was not distributed with this
5 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
7 <!DOCTYPE bindings [
8 <!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
9 %findBarDTD;
10 ]>
12 <bindings id="browser-bindings"
13 xmlns="http://www.mozilla.org/xbl"
14 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
16 <binding id="local-browser" extends="chrome://global/content/bindings/browser.xml#browser">
17 <implementation type="application/javascript"
18 implements="nsIObserver, nsIDOMEventListener, nsIMessageListener">
20 <constructor>
21 <![CDATA[
22 this._frameLoader =
23 this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
24 this._contentViewManager =
25 this._frameLoader.QueryInterface(Components.interfaces.nsIContentViewManager);
27 let prefService =
28 Components.classes["@mozilla.org/preferences-service;1"]
29 .getService(Components.interfaces.nsIPrefBranch);
30 this._cacheRatioWidth =
31 Math.max(1, prefService.getIntPref("toolkit.browser.cacheRatioWidth") / 1000);
32 this._cacheRatioHeight =
33 Math.max(1, prefService.getIntPref("toolkit.browser.cacheRatioHeight") / 1000);
35 if (this._contentViewPrototype) {
36 this._contentViewPrototype.kDieTime = prefService.getIntPref("toolkit.browser.contentViewExpire");
37 }
39 this.messageManager.loadFrameScript("chrome://browser/content/bindings/browser.js", true);
40 this.messageManager.addMessageListener("DOMTitleChanged", this._messageListenerLocal);
41 this.messageManager.addMessageListener("DOMLinkAdded", this._messageListenerLocal);
42 this.messageManager.addMessageListener("pageshow", this._messageListenerLocal);
43 this.messageManager.addMessageListener("pagehide", this._messageListenerLocal);
44 this.messageManager.addMessageListener("DOMPopupBlocked", this._messageListenerLocal);
45 this.messageManager.addMessageListener("MozScrolledAreaChanged", this._messageListenerLocal);
46 this.messageManager.addMessageListener("Content:UpdateDisplayPort", this._messageListenerLocal);
48 this._webProgress._init();
50 // Remove event listeners added by toolkit <browser> binding.
51 this.removeEventListener("pageshow", this.onPageShow, true);
52 this.removeEventListener("pagehide", this.onPageHide, true);
53 this.removeEventListener("DOMPopupBlocked", this.onPopupBlocked, true);
55 this.setAttribute("autoscrollpopup", "autoscrollerid");
56 ]]>
57 </constructor>
59 <field name="_searchEngines">[]</field>
60 <property name="searchEngines"
61 onget="return this._searchEngines"
62 readonly="true"/>
64 <field name="_documentURI">null</field>
65 <property name="documentURI"
66 onget="return this._documentURI ? this._ios.newURI(this._documentURI, null, null) : null"
67 readonly="true"/>
69 <field name="contentWindowId">null</field>
71 <field name="_contentTitle">null</field>
73 <field name="_ios">
74 Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
75 </field>
77 <!--
78 * Point Conversion Routines - browsers may be shifted by UI such that
79 * a client point in an event does not coincide with a css position.
80 * Examples include the notification bar, which pushes the browser down,
81 * or deck movement when forms are positioned above the keyboard.
82 *
83 * Client to browser conversion:
84 *
85 * ptClientToBrowser
86 * Convert client coordinates in device pixels to page-relative
87 * coordinates in CSS pixels.
88 *
89 * @param aClientX, aClientY - client coordinates to convert.
90 * @param aIgnoreScroll ignore root frame scroll.
91 * @param aIgnoreScale ignore current scale factor.
92 * @return { x: converted x coordinate, y: converted y coordinate }
93 *
94 * rectClientToBrowser
95 * Convert a client Rect() in device pixels to page-relative
96 * coordinates in CSS pixels.
97 *
98 * @param aRect - client Rect to convert.
99 * @param aIgnoreScroll ignore root frame scroll.
100 * @param aIgnoreScale ignore current scale factor.
101 * @return converted Rect()
102 *
103 * ctobx, ctoby
104 * Convert individual x and y coordinates.
105 *
106 * @param aX or aY - browser coordinate
107 * @param aIgnoreScroll ignore root frame scroll.
108 * @param aIgnoreScale ignore current scale factor.
109 * @return converted coordinate
110 *
111 * Browser to client conversion:
112 *
113 * ptBrowserToClient
114 * Convert browser coordinates in css pixels to client (screen) coordinates
115 * in device pixels. Useful in positioning UI elements at event targets.
116 *
117 * @param aBrowserX, aBrowserY - browser coordinates to convert.
118 * @param aIgnoreScroll ignore root frame scroll.
119 * @param aIgnoreScale ignore current scale factor.
120 * @return { x: converted x coordinate, y: converted y coordinate }
121 *
122 * msgBrowserToClient
123 * Converts a message manager message with coordinates stored in
124 * aMessage.json.xPos, aMessage.json.yPos.
125 *
126 * @param aMessage - message manager message
127 * @param aIgnoreScroll ignore root frame scroll.
128 * @param aIgnoreScale ignore current scale factor.
129 * @return { x: converted x coordinate, y: converted y coordinate }
130 *
131 * rectBrowserToClient
132 * Converts a rect (left, top, right, bottom).
133 *
134 * @param aRect - rect to convert
135 * @param aIgnoreScroll ignore root frame scroll.
136 * @param aIgnoreScale ignore current scale factor.
137 * @return { left:, top:, right:, bottom: }
138 *
139 * btocx, btocy
140 * Convert individual x and y coordinates.
141 *
142 * @param aX or aY - client coordinate
143 * @param aIgnoreScroll ignore root frame scroll.
144 * @param aIgnoreScale ignore current scale factor.
145 * @return converted coordinate
146 -->
147 <method name="ptClientToBrowser">
148 <parameter name="aClientX"/>
149 <parameter name="aClientY"/>
150 <parameter name="aIgnoreScroll"/>
151 <parameter name="aIgnoreScale"/>
152 <body>
153 <![CDATA[
154 let ignoreScroll = aIgnoreScroll || false;
155 let ignoreScale = aIgnoreScale || false;
157 let bcr = this.getBoundingClientRect();
159 let scrollX = 0;
160 let scrollY = 0;
161 if (!ignoreScroll) {
162 let scroll = this.getRootView().getPosition();
163 scrollX = scroll.x;
164 scrollY = scroll.y;
165 }
167 let scale = 1;
168 if (!ignoreScale) {
169 scale = this.scale;
170 }
172 return {
173 x: (aClientX - bcr.left) / scale + scrollX,
174 y: (aClientY - bcr.top) / scale + scrollY
175 };
176 ]]>
177 </body>
178 </method>
180 <method name="rectClientToBrowser">
181 <parameter name="aClientRect"/>
182 <parameter name="aIgnoreScroll"/>
183 <parameter name="aIgnoreScale"/>
184 <body>
185 <![CDATA[
186 let ignoreScroll = aIgnoreScroll || false;
187 let ignoreScale = aIgnoreScale || false;
189 let scrollX = 0;
190 let scrollY = 0;
191 if (!ignoreScroll) {
192 let scroll = this.getRootView().getPosition();
193 scrollX = scroll.x;
194 scrollY = scroll.y;
195 }
197 let scale = 1;
198 if (!ignoreScale) {
199 scale = this.scale;
200 }
202 let bcr = this.getBoundingClientRect();
203 let clientRect = Rect.fromRect(aClientRect);
204 return new Rect(
205 (clientRect.x - bcr.left) / scale + scrollX,
206 (clientRect.y - bcr.top) / scale + scrollY,
207 clientRect.width / scale,
208 clientRect.height / scale
209 );
210 ]]>
211 </body>
212 </method>
214 <method name="ctobx">
215 <parameter name="aX"/>
216 <parameter name="aIgnoreScroll"/>
217 <parameter name="aIgnoreScale"/>
218 <body>
219 <![CDATA[
220 let y = 0;
221 let result = this.ptClientToBrowser(aX, y, aIgnoreScroll, aIgnoreScale);
222 return result.x;
223 ]]>
224 </body>
225 </method>
227 <method name="ctoby">
228 <parameter name="aY"/>
229 <parameter name="aIgnoreScroll"/>
230 <parameter name="aIgnoreScale"/>
231 <body>
232 <![CDATA[
233 let x = 0;
234 let result = this.ptClientToBrowser(x, aY, aIgnoreScroll, aIgnoreScale);
235 return result.y;
236 ]]>
237 </body>
238 </method>
240 <method name="ptBrowserToClient">
241 <parameter name="aBrowserX"/>
242 <parameter name="aBrowserY"/>
243 <parameter name="aIgnoreScroll"/>
244 <parameter name="aIgnoreScale"/>
245 <body>
246 <![CDATA[
247 let ignoreScroll = aIgnoreScroll || false;
248 let ignoreScale = aIgnoreScale || false;
250 let bcr = this.getBoundingClientRect();
252 let scrollX = 0;
253 let scrollY = 0;
254 if (!ignoreScroll) {
255 let scroll = this.getRootView().getPosition();
256 scrollX = scroll.x;
257 scrollY = scroll.y;
258 }
260 let scale = 1;
261 if (!ignoreScale) {
262 scale = this.scale;
263 }
265 return {
266 x: (aBrowserX * scale - scrollX + bcr.left),
267 y: (aBrowserY * scale - scrollY + bcr.top)
268 };
269 ]]>
270 </body>
271 </method>
273 <method name="msgBrowserToClient">
274 <parameter name="aMessage"/>
275 <parameter name="aIgnoreScroll"/>
276 <parameter name="aIgnoreScale"/>
277 <body>
278 <![CDATA[
279 let x = aMessage.json.xPos;
280 let y = aMessage.json.yPos;
281 return this.ptBrowserToClient(x, y, aIgnoreScroll, aIgnoreScale);
282 ]]>
283 </body>
284 </method>
286 <method name="rectBrowserToClient">
287 <parameter name="aRect"/>
288 <parameter name="aIgnoreScroll"/>
289 <parameter name="aIgnoreScale"/>
290 <body>
291 <![CDATA[
292 let left = aRect.left;
293 let top = aRect.top;
294 let right = aRect.right;
295 let bottom = aRect.bottom;
296 let a = this.ptBrowserToClient(left, top, aIgnoreScroll, aIgnoreScale);
297 let b = this.ptBrowserToClient(right, bottom, aIgnoreScroll, aIgnoreScale);
298 return {
299 left: a.x,
300 top: a.y,
301 right: b.x,
302 bottom: b.y
303 };
304 ]]>
305 </body>
306 </method>
308 <method name="btocx">
309 <parameter name="aX"/>
310 <parameter name="aIgnoreScroll"/>
311 <parameter name="aIgnoreScale"/>
312 <body>
313 <![CDATA[
314 let y = 0;
315 let result = this.ptBrowserToClient(aX, y, aIgnoreScroll, aIgnoreScale);
316 return result.x;
317 ]]>
318 </body>
319 </method>
321 <method name="btocy">
322 <parameter name="aY"/>
323 <parameter name="aIgnoreScroll"/>
324 <parameter name="aIgnoreScale"/>
325 <body>
326 <![CDATA[
327 let x = 0;
328 let result = this.ptBrowserToClient(x, aY, aIgnoreScroll, aIgnoreScale);
329 return result.y;
330 ]]>
331 </body>
332 </method>
334 <property name="scale">
335 <getter><![CDATA[
336 let cwu = this.contentDocument
337 .defaultView
338 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
339 .getInterface(Components.interfaces.nsIDOMWindowUtils);
340 let resx = {}, resy = {};
341 cwu.getResolution(resx, resy);
342 // Resolution set by the apzc and is symmetric.
343 return resx.value;
344 ]]></getter>
345 </property>
347 <field name="_messageListenerLocal"><![CDATA[
348 ({
349 self: this,
350 receiveMessage: function receiveMessage(aMessage) {
351 let self = this.self;
352 let json = aMessage.json;
354 switch (aMessage.name) {
355 case "DOMPopupBlocked":
356 self.onPopupBlocked(aMessage);
357 break;
359 case "pageshow":
360 self.onPageShow(aMessage);
362 if (!self.mIconURL && self._documentURI) {
363 let iconURL = null;
364 if (self.shouldLoadFavicon()) {
365 // Use documentURI in the favicon construction so that we
366 // do the right thing with about:-style error pages. Bug 515188
367 iconURL = self.documentURI.prePath + "/favicon.ico";
368 }
369 self.loadFavicon(iconURL, null);
370 }
371 break;
373 case "pagehide":
374 self.onPageHide(aMessage);
375 break;
377 case "DOMTitleChanged":
378 self._contentTitle = json.title;
379 break;
381 case "DOMLinkAdded":
382 // ignore results from subdocuments
383 if (json.windowId != self.contentWindowId)
384 return;
386 let linkType = self._getLinkType(json);
387 switch(linkType) {
388 case "icon":
389 self.loadFavicon(json.href, json.charset);
390 break;
391 case "search":
392 self._searchEngines.push({ title: json.title, href: json.href });
393 break;
394 }
395 break;
397 case "MozScrolledAreaChanged": {
398 self._contentDocumentWidth = json.width;
399 self._contentDocumentHeight = json.height;
400 self._contentDocumentLeft = (json.left < 0) ? json.left : 0;
402 // Recalculate whether the visible area is actually in bounds
403 let view = self.getRootView();
404 view.scrollBy(0, 0);
405 break;
406 }
408 case "Content:UpdateDisplayPort": {
409 // Recalculate whether the visible area is actually in bounds
410 let view = self.getRootView();
411 view.scrollBy(0, 0);
412 view._updateCacheViewport();
413 break;
414 }
415 }
416 }
417 })
418 ]]></field>
420 <method name="loadFavicon">
421 <parameter name="aURL"/>
422 <parameter name="aCharset"/>
423 <body><![CDATA[
424 try { // newURI call is throwing for chrome URI
425 let iconURI = this._ios.newURI(aURL, aCharset, null);
426 if (gFaviconService.isFailedFavicon(iconURI))
427 return;
429 gFaviconService.setAndFetchFaviconForPage(this.currentURI, iconURI, true,
430 gFaviconService.FAVICON_LOAD_NON_PRIVATE);
431 this.mIconURL = iconURI.spec;
432 } catch (e) {
433 this.mIconURL = null;
434 }
435 ]]></body>
436 </method>
438 <method name="shouldLoadFavicon">
439 <body><![CDATA[
440 let docURI = this.documentURI;
441 return (docURI && ("schemeIs" in docURI) &&
442 (docURI.schemeIs("http") || docURI.schemeIs("https")));
443 ]]></body>
444 </method>
446 <method name="_getLinkType">
447 <parameter name="aLink" />
448 <body><![CDATA[
449 let type = "";
450 if (/\bicon\b/i.test(aLink.rel)) {
451 type = "icon";
452 }
453 else if (/\bsearch\b/i.test(aLink.rel) && aLink.type && aLink.title) {
454 let linkType = aLink.type.replace(/^\s+|\s*(?:;.*)?$/g, "").toLowerCase();
455 if (linkType == "application/opensearchdescription+xml" && /^(?:https?|ftp):/i.test(aLink.href)) {
456 type = "search";
457 }
458 }
460 return type;
461 ]]></body>
462 </method>
464 <field name="_webProgress"><![CDATA[
465 ({
466 _browser: this,
468 _init: function() {
469 this._browser.messageManager.addMessageListener("Content:StateChange", this);
470 this._browser.messageManager.addMessageListener("Content:LocationChange", this);
471 this._browser.messageManager.addMessageListener("Content:SecurityChange", this);
472 },
474 receiveMessage: function(aMessage) {
475 let json = aMessage.json;
476 switch (aMessage.name) {
477 case "Content:StateChange":
478 this._browser.updateWindowId(json.contentWindowId);
479 break;
481 case "Content:LocationChange":
482 try {
483 let locationURI = this._browser._ios.newURI(json.location, null, null);
484 this._browser.webNavigation._currentURI = locationURI;
485 this._browser.webNavigation.canGoBack = json.canGoBack;
486 this._browser.webNavigation.canGoForward = json.canGoForward;
487 this._browser._charset = json.charset;
488 } catch(e) {}
490 if (this._browser.updateWindowId(json.contentWindowId)) {
491 this._browser._documentURI = json.documentURI;
492 this._browser._searchEngines = [];
493 }
494 break;
496 case "Content:SecurityChange":
497 let serhelper = Components.classes["@mozilla.org/network/serialization-helper;1"]
498 .getService(Components.interfaces.nsISerializationHelper);
499 let SSLStatus = json.SSLStatusAsString ? serhelper.deserializeObject(json.SSLStatusAsString) : null;
500 if (SSLStatus) {
501 SSLStatus.QueryInterface(Components.interfaces.nsISSLStatus);
502 // We must check the Extended Validation (EV) state here, on the chrome
503 // process, because NSS is needed for that determination.
504 if (SSLStatus && SSLStatus.isExtendedValidation) {
505 json.state |= Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
506 }
507 }
509 let data = this._getIdentityData(SSLStatus);
510 this._browser.updateWindowId(json.contentWindowId);
511 break;
512 }
513 },
515 /**
516 * Helper to parse out the important parts of the SSL cert for use in constructing
517 * identity UI strings
518 */
519 _getIdentityData: function(status) {
520 let result = {};
522 if (status) {
523 let cert = status.serverCert;
525 // Human readable name of Subject
526 result.subjectOrg = cert.organization;
528 // SubjectName fields, broken up for individual access
529 if (cert.subjectName) {
530 result.subjectNameFields = {};
531 cert.subjectName.split(",").forEach(function(v) {
532 var field = v.split("=");
533 if (field[1])
534 this[field[0]] = field[1];
535 }, result.subjectNameFields);
537 // Call out city, state, and country specifically
538 result.city = result.subjectNameFields.L;
539 result.state = result.subjectNameFields.ST;
540 result.country = result.subjectNameFields.C;
541 }
543 // Human readable name of Certificate Authority
544 result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
546 if (!this._overrideService)
547 this._overrideService = Components.classes["@mozilla.org/security/certoverride;1"]
548 .getService(Components.interfaces.nsICertOverrideService);
550 // Check whether this site is a security exception.
551 let currentURI = this._browser.webNavigation._currentURI;
552 if (currentURI) {
553 result.isException = this._overrideService.hasMatchingOverride(currentURI.asciiHost, currentURI.port, cert, {}, {});
554 } else {
555 result.isException = false;
556 }
557 }
559 return result;
560 }
561 })
562 ]]></field>
564 <property name="webProgress"
565 readonly="true"
566 onget="return null"/>
568 <method name="onPageShow">
569 <parameter name="aMessage"/>
570 <body>
571 <![CDATA[
572 this.attachFormFill();
573 if (this.pageReport) {
574 var json = aMessage.json;
575 var i = 0;
576 while (i < this.pageReport.length) {
577 // Filter out irrelevant reports.
578 if (this.pageReport[i].requestingWindowId == json.windowId)
579 i++;
580 else
581 this.pageReport.splice(i, 1);
582 }
583 if (this.pageReport.length == 0) {
584 this.pageReport = null;
585 this.updatePageReport();
586 }
587 }
588 ]]>
589 </body>
590 </method>
592 <method name="onPageHide">
593 <parameter name="aMessage"/>
594 <body>
595 <![CDATA[
596 if (this.pageReport) {
597 this.pageReport = null;
598 this.updatePageReport();
599 }
600 // Delete the feeds cache if we're hiding the topmost page
601 // (as opposed to one of its iframes).
602 if (this.feeds && aMessage.target == this)
603 this.feeds = null;
605 this._contentWindowWidth = aMessage.json.contentWindowWidth;
606 this._contentWindowHeight = aMessage.json.contentWindowHeight;
607 ]]>
608 </body>
609 </method>
611 <method name="onPopupBlocked">
612 <parameter name="aMessage"/>
613 <body>
614 <![CDATA[
615 if (!this.pageReport) {
616 this.pageReport = [];
617 }
619 let json = aMessage.json;
620 // XXX Replacing requestingWindow && requestingDocument affects
621 // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#500
622 var obj = {
623 requestingWindowId: json.windowId,
624 popupWindowURI: this._ios.newURI(json.popupWindowURI.spec, json.popupWindowURI.charset, null),
625 popupWindowFeatures: json.popupWindowFeatures,
626 popupWindowName: json.popupWindowName
627 };
629 this.pageReport.push(obj);
630 this.pageReport.reported = false;
631 this.updatePageReport();
632 ]]>
633 </body>
634 </method>
636 <field name="_frameLoader">null</field>
637 <field name="_contentViewManager">null</field>
639 <!--
640 * Returns the current content viewport bounds in browser coordinates
641 * taking into account zoom and scroll.
642 *
643 * @return Rect()
644 -->
645 <property name="contentViewportBounds">
646 <getter><![CDATA[
647 return this.rectClientToBrowser(this.getBoundingClientRect());
648 ]]></getter>
649 </property>
651 <!-- Dimensions of content window -->
652 <field name="_contentWindowWidth">0</field>
653 <field name="_contentWindowHeight">0</field>
654 <property name="contentWindowWidth"
655 onget="return this._contentWindowWidth;"
656 readonly="true"/>
657 <property name="contentWindowHeight"
658 onget="return this._contentWindowHeight;"
659 readonly="true"/>
661 <!-- Dimensions of content document -->
662 <field name="_contentDocumentWidth">0</field>
663 <field name="_contentDocumentHeight">0</field>
664 <property name="contentDocumentWidth"
665 onget="return this._contentDocumentWidth;"
666 readonly="true"/>
667 <property name="contentDocumentHeight"
668 onget="return this._contentDocumentHeight;"
669 readonly="true"/>
671 <!-- If this attribute is negative this indicate the document is rtl and
672 some operations like panning or calculating the cache area should
673 take it into account. This is useless for non-remote browser -->
674 <field name="_contentDocumentLeft">0</field>
676 <!-- These counters are used to update the cached viewport after they reach a certain
677 threshold when scrolling -->
678 <field name="_cacheRatioWidth">1</field>
679 <field name="_cacheRatioHeight">1</field>
681 <!-- Used in remote tabs only. -->
682 <method name="_updateCSSViewport">
683 <body/>
684 </method>
686 <!-- Sets size of CSS viewport, which affects how page is layout. -->
687 <method name="setWindowSize">
688 <parameter name="width"/>
689 <parameter name="height"/>
690 <body>
691 <![CDATA[
692 this._contentWindowWidth = width;
693 this._contentWindowHeight = height;
694 this.messageManager.sendAsyncMessage("Content:SetWindowSize", {
695 width: width,
696 height: height
697 });
699 // If the window size is changing, make sure the displayport is in sync
700 this.getRootView()._updateCacheViewport();
701 ]]>
702 </body>
703 </method>
705 <method name="getRootView">
706 <body>
707 <![CDATA[
708 return this._contentView;
709 ]]>
710 </body>
711 </method>
713 <field name="_contentViewPrototype"><![CDATA[
714 ({
715 _scrollbox: null,
717 init: function(aElement) {
718 this._scrollbox = aElement.scrollBoxObject;
719 },
721 isRoot: function() {
722 return false;
723 },
725 scrollBy: function(x, y) {
726 this._scrollbox.scrollBy(x,y);
727 },
729 scrollTo: function(x, y) {
730 this._scrollbox.scrollTo(x,y);
731 },
733 getPosition: function() {
734 let x = {}, y = {};
735 this._scrollbox.getPosition(x, y);
736 return { x: x.value, y: y.value };
737 }
738 })
739 ]]>
740 </field>
742 <method name="getViewAt">
743 <parameter name="x"/>
744 <parameter name="y"/>
745 <body>
746 <![CDATA[
747 let cwu = this.contentDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
748 .getInterface(Components.interfaces.nsIDOMWindowUtils);
749 let elt = cwu.elementFromPoint(x, y, false, false);
751 while (elt && !elt.scrollBoxObject)
752 elt = elt.parentNode;
754 if (!elt)
755 return this._contentView;
757 let cv = Object.create(this._contentViewPrototype);
758 cv.init(elt);
759 return cv;
760 ]]>
761 </body>
762 </method>
764 <field name="_contentView"><![CDATA[
765 ({
766 self: this,
768 _updateCacheViewport: function() {
769 },
771 isRoot: function() {
772 return true;
773 },
775 scrollBy: function(x, y) {
776 let self = this.self;
777 self.contentWindow.scrollBy(x, y);
778 },
780 scrollTo: function(x, y) {
781 let self = this.self;
782 x = Math.floor(Math.max(0, Math.min(self.contentDocumentWidth, x)));
783 y = Math.floor(Math.max(0, Math.min(self.contentDocumentHeight, y)));
784 self.contentWindow.scrollTo(x, y);
785 },
787 getPosition: function() {
788 let self = this.self;
789 let scrollX = {}, scrollY = {};
790 let cwu = self.contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
791 getInterface(Components.interfaces.nsIDOMWindowUtils);
792 cwu.getScrollXY(false, scrollX, scrollY);
793 return { x: scrollX.value, y: scrollY.value };
794 },
796 toString: function() {
797 return "[View Local]";
798 }
799 })
800 ]]>
801 </field>
803 <method name="updateWindowId">
804 <parameter name="aNewId"/>
805 <body><![CDATA[
806 if (this.contentWindowId != aNewId) {
807 this.contentWindowId = aNewId;
808 return true;
809 }
810 return false;
811 ]]></body>
812 </method>
814 <field name="_active">false</field>
815 <property name="active" onget="return this._active;">
816 <setter><![CDATA[
817 // Do not change displayport on local tabs!
818 this._active = val;
819 this.docShellIsActive = this._active;
820 ]]></setter>
821 </property>
822 </implementation>
823 </binding>
825 <binding id="remote-browser" extends="#local-browser">
826 <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener">
827 <property name="autoscrollEnabled">
828 <getter>
829 <![CDATA[
830 throw "autoscrollEnabled: Supports Remote?";
831 ]]>
832 </getter>
833 </property>
835 <property name="docShell"
836 readonly="true">
837 <getter><![CDATA[
838 return {
839 forcedCharset : this._charset,
840 parentCharset : "",
841 parentCharsetSource : 0
842 }
843 ]]></getter>
844 </property>
846 <field name="_contentTitle">null</field>
848 <property name="contentTitle"
849 onget="return this._contentTitle;"
850 readonly="true"/>
852 <field name="_remoteWebNavigation">null</field>
853 <property name="webNavigation" readonly="true">
854 <getter>
855 <![CDATA[
856 if (!this._remoteWebNavigation) {
857 let jsm = "resource://gre/modules/RemoteWebNavigation.jsm";
858 let RemoteWebNavigation = Components.utils.import(jsm, {}).RemoteWebNavigation;
859 this._remoteWebNavigation = new RemoteWebNavigation(this);
860 }
861 return this._remoteWebNavigation;
862 ]]>
863 </getter>
864 </property>
866 <property name="contentWindow"
867 readonly="true"
868 onget="return null"/>
870 <property name="sessionHistory"
871 onget="return null"
872 readonly="true"/>
874 <property name="markupDocumentViewer"
875 onget="return null"
876 readonly="true"/>
878 <property name="contentViewerEdit"
879 onget="return null"
880 readonly="true"/>
882 <property name="contentViewerFile"
883 onget="return null"
884 readonly="true"/>
886 <field name="_charset"></field>
888 <constructor>
889 <![CDATA[
890 this.messageManager.addMessageListener("scroll", this._messageListenerRemote);
891 ]]>
892 </constructor>
894 <field name="scrollSync">true</field>
896 <field name="_messageListenerRemote"><![CDATA[
897 ({
898 self: this,
899 receiveMessage: function receiveMessage(aMessage) {
900 let self = this.self;
901 let json = aMessage.json;
903 switch (aMessage.name) {
904 case "scroll":
905 if (!json.isRoot)
906 return;
907 if (!self.scrollSync)
908 return;
909 this.doScroll(json.scrollOffset.x, json.scrollOffset.y, 0);
910 break;
911 }
912 },
914 doScroll: function doScroll(aX, aY, aCount) {
915 let self = this.self;
917 // Use floor so that we always guarantee top-left corner of content is visible.
918 let view = self.getRootView();
919 view.scrollTo(Math.floor(aX * self.scale), Math.floor(aY * self.scale));
921 let position = view.getPosition();
922 if ((position.x != aX * self.scale || position.y != aY * self.scale) && aCount < 3) {
923 setTimeout((function() {
924 this.doScroll(aX, aY, ++aCount);
925 }).bind(this), 0);
926 }
927 }
928 })
929 ]]></field>
931 <!-- Keep a store of temporary content views. -->
932 <field name="_contentViews">({})</field>
934 <!-- There is a point before a page has loaded where a root content view
935 may not exist. We use this so that we don't have to worry about doing
936 an if check every time we want to scroll. -->
937 <field name="_contentNoop"><![CDATA[
938 ({
939 _updateCacheViewport: function() {},
940 _getViewportSize: function() {},
942 isRoot: function() {
943 return true;
944 },
946 _scale: 1,
947 _setScale: function(scale) {},
948 scrollBy: function(x, y) {},
949 scrollTo: function(x, y) {},
950 getPosition: function() {
951 return { x: 0, y: 0 };
952 }
953 })
954 ]]></field>
956 <field name="_contentViewPrototype"><![CDATA[
957 ({
958 self: this,
959 _id: null,
960 _contentView: null,
961 _timeout: null,
962 _pixelsPannedSinceRefresh: { x: 0, y: 0 },
963 _lastPanTime: 0,
965 kDieTime: 3000,
967 /**
968 * Die if we haven't panned in a while.
969 *
970 * Since we keep a map of active content views, we need to regularly
971 * check if they are necessary so that every single thing the user
972 * pans is not kept in memory forever.
973 */
974 _dieIfOld: function() {
975 if (Date.now() - this._lastPanTime >= this.kDieTime)
976 this._die();
977 else
978 // This doesn't need to be exact, just be sure to clean up at some point.
979 this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime);
980 },
982 /** Cleanup after ourselves. */
983 _die: function() {
984 let timeout = this._timeout;
985 if (timeout) {
986 clearTimeout(timeout);
987 this._timeout = null;
988 }
990 if (this._contentView && Math.abs(this._pixelsPannedSinceRefresh) > 0)
991 this._updateCacheViewport();
993 // We expect contentViews to contain our ID. If not, something bad
994 // happened.
995 delete this.self._contentViews[this._id];
996 },
998 /**
999 * Given the cache size and the viewport size, this determines where the cache
1000 * should start relative to the scroll position. This adjusts the position based
1001 * on which direction the user is panning, so that we use our cache as
1002 * effectively as possible.
1003 *
1004 * @param aDirection Negative means user is panning to the left or above
1005 * Zero means user did not pan
1006 * Positive means user is panning to the right or below
1007 * @param aViewportSize The width or height of the viewport
1008 * @param aCacheSize The width or height of the displayport
1009 */
1010 _getRelativeCacheStart: function(aDirection, aViewportSize, aCacheSize) {
1011 // Remember that this is relative to the viewport scroll position.
1012 // Let's assume we are thinking about the y-axis.
1013 // The extreme cases:
1014 // |0| would mean that there is no content available above
1015 // |aViewportSize - aCacheSize| would mean no content available below
1016 //
1017 // Taking the average of the extremes puts equal amounts of cache on the
1018 // top and bottom of the viewport. If we think of this like a weighted
1019 // average, .5 is the sweet spot where equals amounts of content are
1020 // above and below the visible area.
1021 //
1022 // This weight is therefore how much of the cache is above (or to the
1023 // left) the visible area.
1024 let cachedAbove = .5;
1026 // If panning down, leave only 25% of the non-visible cache above.
1027 if (aDirection > 0)
1028 cachedAbove = .25;
1030 // If panning up, Leave 75% of the non-visible cache above.
1031 if (aDirection < 0)
1032 cachedAbove = .75;
1034 return (aViewportSize - aCacheSize) * cachedAbove;
1035 },
1037 /** Determine size of the pixel cache. */
1038 _getCacheSize: function(viewportSize) {
1039 let self = this.self;
1040 let contentView = this._contentView;
1042 let cacheWidth = self._cacheRatioWidth * viewportSize.width;
1043 let cacheHeight = self._cacheRatioHeight * viewportSize.height;
1044 let contentSize = this._getContentSize();
1045 let contentWidth = contentSize.width;
1046 let contentHeight = contentSize.height;
1048 // There are common cases, such as long skinny pages, where our cache size is
1049 // bigger than our content size. In those cases, we take that sliver of leftover
1050 // space and apply it to the other dimension.
1051 if (contentWidth < cacheWidth) {
1052 cacheHeight += (cacheWidth - contentWidth) * cacheHeight / cacheWidth;
1053 cacheWidth = contentWidth;
1054 } else if (contentHeight < cacheHeight) {
1055 cacheWidth += (cacheHeight - contentHeight) * cacheWidth / cacheHeight;
1056 cacheHeight = contentHeight;
1057 }
1059 return { width: cacheWidth, height: cacheHeight };
1060 },
1062 _sendDisplayportUpdate: function(scrollX, scrollY) {
1063 let self = this.self;
1064 if (!self.active)
1065 return;
1067 let contentView = this._contentView;
1068 let viewportSize = this._getViewportSize();
1069 let cacheSize = this._getCacheSize(viewportSize);
1070 let cacheX = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.x, viewportSize.width, cacheSize.width) + contentView.scrollX;
1071 let cacheY = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.y, viewportSize.height, cacheSize.height) + contentView.scrollY;
1072 let contentSize = this._getContentSize();
1074 // Use our pixels efficiently and don't try to cache things outside of content
1075 // boundaries (The left bound can be negative because of RTL).
1077 let rootScale = self.scale;
1078 let leftBound = self._contentDocumentLeft * rootScale;
1079 let bounds = new Rect(leftBound, 0, contentSize.width, contentSize.height);
1080 let displayport = new Rect(cacheX, cacheY, cacheSize.width, cacheSize.height);
1081 displayport.translateInside(bounds);
1083 self.messageManager.sendAsyncMessage("Content:SetCacheViewport", {
1084 scrollX: Math.round(scrollX) / rootScale,
1085 scrollY: Math.round(scrollY) / rootScale,
1086 x: Math.round(displayport.x) / rootScale,
1087 y: Math.round(displayport.y) / rootScale,
1088 w: Math.round(displayport.width) / rootScale,
1089 h: Math.round(displayport.height) / rootScale,
1090 scale: rootScale,
1091 id: contentView.id
1092 });
1094 this._pixelsPannedSinceRefresh.x = 0;
1095 this._pixelsPannedSinceRefresh.y = 0;
1096 },
1098 _updateCSSViewport: function() {
1099 let contentView = this._contentView;
1100 this._sendDisplayportUpdate(contentView.scrollX,
1101 contentView.scrollY);
1102 },
1104 /**
1105 * The cache viewport is what parts of content is cached in the parent process for
1106 * fast scrolling. This syncs that up with the current projection viewport.
1107 */
1108 _updateCacheViewport: function() {
1109 // Do not update scroll values for content.
1110 if (this.isRoot())
1111 this._sendDisplayportUpdate(-1, -1);
1112 else {
1113 let contentView = this._contentView;
1114 this._sendDisplayportUpdate(contentView.scrollX,
1115 contentView.scrollY);
1116 }
1117 },
1119 _getContentSize: function() {
1120 let self = this.self;
1121 return { width: this._contentView.contentWidth,
1122 height: this._contentView.contentHeight };
1123 },
1125 _getViewportSize: function() {
1126 let self = this.self;
1127 if (this.isRoot()) {
1128 let bcr = self.getBoundingClientRect();
1129 return { width: bcr.width, height: bcr.height };
1130 } else {
1131 return { width: this._contentView.viewportWidth,
1132 height: this._contentView.viewportHeight };
1133 }
1134 },
1136 init: function(contentView) {
1137 let self = this.self;
1139 this._contentView = contentView;
1140 this._id = contentView.id;
1141 this._scale = 1;
1142 self._contentViews[this._id] = this;
1144 if (!this.isRoot()) {
1145 // Non-root content views are short lived.
1146 this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime);
1147 // This iframe may not have a display port yet, so build up a cache
1148 // immediately.
1149 this._updateCacheViewport();
1150 }
1151 },
1153 isRoot: function() {
1154 return this.self._contentViewManager.rootContentView == this._contentView;
1155 },
1157 scrollBy: function(x, y) {
1158 let self = this.self;
1160 // Bounding content rectangle is in device pixels
1161 let contentView = this._contentView;
1162 let viewportSize = this._getViewportSize();
1163 let contentSize = this._getContentSize();
1164 // Calculate document dimensions in device pixels
1165 let scrollRangeX = contentSize.width - viewportSize.width;
1166 let scrollRangeY = contentSize.height - viewportSize.height;
1168 let leftOffset = self._contentDocumentLeft * this._scale;
1169 x = Math.floor(Math.max(leftOffset, Math.min(scrollRangeX + leftOffset, contentView.scrollX + x))) - contentView.scrollX;
1170 y = Math.floor(Math.max(0, Math.min(scrollRangeY, contentView.scrollY + y))) - contentView.scrollY;
1172 if (x == 0 && y == 0)
1173 return;
1175 contentView.scrollBy(x, y);
1177 this._lastPanTime = Date.now();
1179 this._pixelsPannedSinceRefresh.x += x;
1180 this._pixelsPannedSinceRefresh.y += y;
1181 if (Math.abs(this._pixelsPannedSinceRefresh.x) > 20 ||
1182 Math.abs(this._pixelsPannedSinceRefresh.y) > 20)
1183 this._updateCacheViewport();
1184 },
1186 scrollTo: function(x, y) {
1187 let contentView = this._contentView;
1188 this.scrollBy(x - contentView.scrollX, y - contentView.scrollY);
1189 },
1191 _setScale: function _setScale(scale) {
1192 this._scale = scale;
1193 this._contentView.setScale(scale, scale);
1194 },
1196 getPosition: function() {
1197 let contentView = this._contentView;
1198 return { x: contentView.scrollX, y: contentView.scrollY };
1199 }
1200 })
1201 ]]>
1202 </field>
1204 <!-- The ratio of CSS pixels to device pixels. -->
1205 <property name="scale">
1206 <getter><![CDATA[
1207 return this.getRootView()._scale;
1208 ]]></getter>
1209 <setter><![CDATA[
1210 if (val <= 0 || val == this.scale)
1211 return;
1213 let rootView = this.getRootView();
1214 rootView._setScale(val);
1216 return val;
1217 ]]></setter>
1218 </property>
1220 <method name="_getView">
1221 <parameter name="contentView"/>
1222 <body>
1223 <![CDATA[
1224 if (!contentView) return null;
1226 // See if we have cached it.
1227 let id = contentView.id;
1228 let jsContentView = this._contentViews[id];
1229 if (jsContentView) {
1230 // Content view may have changed if it became inactive for a
1231 // little while.
1232 jsContentView._contentView = contentView;
1233 return jsContentView;
1234 }
1236 // Not cached. Create it.
1237 jsContentView = Object.create(this._contentViewPrototype);
1238 jsContentView.init(contentView);
1239 return jsContentView;
1240 ]]>
1241 </body>
1242 </method>
1244 <!-- Get root content view. -->
1245 <method name="getRootView">
1246 <body>
1247 <![CDATA[
1248 let contentView = this._contentViewManager.rootContentView;
1249 return this._getView(contentView) || this._contentNoop;
1250 ]]>
1251 </body>
1252 </method>
1254 <!-- Get contentView for position (x, y) relative to the browser element -->
1255 <method name="getViewAt">
1256 <parameter name="x"/>
1257 <parameter name="y"/>
1258 <body>
1259 <![CDATA[
1260 let manager = this._contentViewManager;
1261 let contentView = manager.getContentViewsIn(x, y, 0, 0, 0, 0)[0] ||
1262 manager.rootContentView;
1263 return this._getView(contentView);
1264 ]]>
1265 </body>
1266 </method>
1268 <!-- Synchronize the CSS viewport with the projection viewport. -->
1269 <method name="_updateCSSViewport">
1270 <body>
1271 <![CDATA[
1272 let rootView = this.getRootView();
1273 rootView._updateCSSViewport();
1274 ]]>
1275 </body>
1276 </method>
1278 <property name="active" onget="return this._active;">
1279 <setter><![CDATA[
1280 this._active = val;
1281 let keepVisible = false;
1282 this.messageManager.sendAsyncMessage((val ? "Content:Activate" : "Content:Deactivate"), { keepviewport: keepVisible });
1283 if (val)
1284 this.getRootView()._updateCacheViewport();
1285 ]]></setter>
1286 </property>
1288 <field name="_remoteFinder">null</field>
1289 <property name="finder" readonly="true">
1290 <getter><![CDATA[
1291 if (!this._remoteFinder) {
1292 let jsm = "resource://gre/modules/RemoteFinder.jsm";
1293 let RemoteFinder = Cu.import(jsm, {}).RemoteFinder;
1294 this._remoteFinder = new RemoteFinder(this);
1295 }
1296 return this._remoteFinder;
1297 ]]></getter>
1298 </property>
1299 </implementation>
1301 </binding>
1303 </bindings>