gecko/browser/metro/base/content/bindings/browser.xml

1277 lines
44 KiB
XML

<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings [
<!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
%findBarDTD;
]>
<bindings id="browser-bindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="local-browser" extends="chrome://global/content/bindings/browser.xml#browser">
<implementation type="application/javascript"
implements="nsIAccessibleProvider, nsIObserver, nsIDOMEventListener, nsIMessageListener">
<constructor>
<![CDATA[
this._frameLoader =
this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
this._contentViewManager =
this._frameLoader.QueryInterface(Components.interfaces.nsIContentViewManager);
let prefService =
Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
this._cacheRatioWidth =
Math.max(1, prefService.getIntPref("toolkit.browser.cacheRatioWidth") / 1000);
this._cacheRatioHeight =
Math.max(1, prefService.getIntPref("toolkit.browser.cacheRatioHeight") / 1000);
if (this._contentViewPrototype) {
this._contentViewPrototype.kDieTime = prefService.getIntPref("toolkit.browser.contentViewExpire");
}
this.messageManager.loadFrameScript("chrome://browser/content/bindings/browser.js", true);
this.messageManager.addMessageListener("DOMTitleChanged", this._messageListenerLocal);
this.messageManager.addMessageListener("DOMLinkAdded", this._messageListenerLocal);
this.messageManager.addMessageListener("pageshow", this._messageListenerLocal);
this.messageManager.addMessageListener("pagehide", this._messageListenerLocal);
this.messageManager.addMessageListener("DOMPopupBlocked", this._messageListenerLocal);
this.messageManager.addMessageListener("MozScrolledAreaChanged", this._messageListenerLocal);
this.messageManager.addMessageListener("Content:UpdateDisplayPort", this._messageListenerLocal);
this._webProgress._init();
// Remove event listeners added by toolkit <browser> binding.
this.removeEventListener("pageshow", this.onPageShow, true);
this.removeEventListener("pagehide", this.onPageHide, true);
this.removeEventListener("DOMPopupBlocked", this.onPopupBlocked, true);
this.setAttribute("autoscrollpopup", "autoscrollerid");
]]>
</constructor>
<field name="_searchEngines">[]</field>
<property name="searchEngines"
onget="return this._searchEngines"
readonly="true"/>
<field name="_documentURI">null</field>
<property name="documentURI"
onget="return this._documentURI ? this._ios.newURI(this._documentURI, null, null) : null"
readonly="true"/>
<field name="contentWindowId">null</field>
<property name="messageManager"
onget="return this._frameLoader.messageManager;"
readonly="true"/>
<field name="_contentTitle">null</field>
<field name="_ios">
Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
</field>
<!--
* Point Conversion Routines - browsers may be shifted by UI such that
* a client point in an event does not coincide with a css position.
* Examples include the notification bar, which pushes the browser down,
* or deck movement when forms are positioned above the keyboard.
*
* Client to browser conversion:
*
* ptClientToBrowser
* Convert client coordinates in device pixels to page-relative
* coordinates in CSS pixels.
*
* @param aClientX, aClientY - client coordinates to convert.
* @param aIgnoreScroll ignore root frame scroll.
* @param aIgnoreScale ignore current scale factor.
* @return { x: converted x coordinate, y: converted y coordinate }
*
* ctobx, ctoby
* Convert individual x and y coordinates.
*
* @param aX or aY - browser coordinate
* @param aIgnoreScroll ignore root frame scroll.
* @param aIgnoreScale ignore current scale factor.
* @return converted coordinate
*
* Browser to client conversion:
*
* ptBrowserToClient
* Convert browser coordinates in css pixels to client (screen) coordinates
* in device pixels. Useful in positioning UI elements at event targets.
*
* @param aBrowserX, aBrowserY - browser coordinates to convert.
* @param aIgnoreScroll ignore root frame scroll.
* @param aIgnoreScale ignore current scale factor.
* @return { x: converted x coordinate, y: converted y coordinate }
*
* msgBrowserToClient
* Converts a message manager message with coordinates stored in
* aMessage.json.xPos, aMessage.json.yPos.
*
* @param aMessage - message manager message
* @param aIgnoreScroll ignore root frame scroll.
* @param aIgnoreScale ignore current scale factor.
* @return { x: converted x coordinate, y: converted y coordinate }
*
* rectBrowserToClient
* Converts a rect (left, top, right, bottom).
*
* @param aRect - rect to convert
* @param aIgnoreScroll ignore root frame scroll.
* @param aIgnoreScale ignore current scale factor.
* @return { left:, top:, right:, bottom: }
*
* btocx, btocy
* Convert individual x and y coordinates.
*
* @param aX or aY - client coordinate
* @param aIgnoreScroll ignore root frame scroll.
* @param aIgnoreScale ignore current scale factor.
* @return converted coordinate
-->
<method name="ptClientToBrowser">
<parameter name="aClientX"/>
<parameter name="aClientY"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let ignoreScroll = aIgnoreScroll || false;
let ignoreScale = aIgnoreScale || false;
let bcr = this.getBoundingClientRect();
let scrollX = 0;
let scrollY = 0;
if (!ignoreScroll) {
let scroll = this.getRootView().getPosition();
scrollX = scroll.x;
scrollY = scroll.y;
}
let scale = 1;
if (!ignoreScale) {
scale = this.scale;
}
return {
x: (aClientX + scrollX - bcr.left) / scale,
y: (aClientY + scrollY - bcr.top) / scale
};
]]>
</body>
</method>
<method name="ctobx">
<parameter name="aX"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let y = 0;
let result = this.ptClientToBrowser(aX, y, aIgnoreScroll, aIgnoreScale);
return result.x;
]]>
</body>
</method>
<method name="ctoby">
<parameter name="aY"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let x = 0;
let result = this.ptClientToBrowser(x, aY, aIgnoreScroll, aIgnoreScale);
return result.y;
]]>
</body>
</method>
<method name="ptBrowserToClient">
<parameter name="aBrowserX"/>
<parameter name="aBrowserY"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let ignoreScroll = aIgnoreScroll || false;
let ignoreScale = aIgnoreScale || false;
let bcr = this.getBoundingClientRect();
let scrollX = 0;
let scrollY = 0;
if (!ignoreScroll) {
let scroll = this.getRootView().getPosition();
scrollX = scroll.x;
scrollY = scroll.y;
}
let scale = 1;
if (!ignoreScale) {
scale = this.scale;
}
return {
x: (aBrowserX * scale - scrollX + bcr.left),
y: (aBrowserY * scale - scrollY + bcr.top)
};
]]>
</body>
</method>
<method name="msgBrowserToClient">
<parameter name="aMessage"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let x = aMessage.json.xPos;
let y = aMessage.json.yPos;
return this.ptBrowserToClient(x, y, aIgnoreScroll, aIgnoreScale);
]]>
</body>
</method>
<method name="rectBrowserToClient">
<parameter name="aRect"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let left = aRect.left;
let top = aRect.top;
let right = aRect.right;
let bottom = aRect.bottom;
let a = this.ptBrowserToClient(left, top, aIgnoreScroll, aIgnoreScale);
let b = this.ptBrowserToClient(right, bottom, aIgnoreScroll, aIgnoreScale);
return {
left: a.x,
top: a.y,
right: b.x,
bottom: b.y
};
]]>
</body>
</method>
<method name="btocx">
<parameter name="aX"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let y = 0;
let result = this.ptBrowserToClient(aX, y, aIgnoreScroll, aIgnoreScale);
return result.x;
]]>
</body>
</method>
<method name="btocy">
<parameter name="aY"/>
<parameter name="aIgnoreScroll"/>
<parameter name="aIgnoreScale"/>
<body>
<![CDATA[
let x = 0;
let result = this.ptBrowserToClient(x, aY, aIgnoreScroll, aIgnoreScale);
return result.y;
]]>
</body>
</method>
<field name="_messageListenerLocal"><![CDATA[
({
self: this,
receiveMessage: function receiveMessage(aMessage) {
let self = this.self;
let json = aMessage.json;
switch (aMessage.name) {
case "DOMPopupBlocked":
self.onPopupBlocked(aMessage);
break;
case "pageshow":
self.onPageShow(aMessage);
if (!self.mIconURL && self._documentURI) {
let iconURL = null;
if (self.shouldLoadFavicon()) {
// Use documentURI in the favicon construction so that we
// do the right thing with about:-style error pages. Bug 515188
iconURL = self.documentURI.prePath + "/favicon.ico";
}
self.loadFavicon(iconURL, null);
}
break;
case "pagehide":
self.onPageHide(aMessage);
break;
case "DOMTitleChanged":
self._contentTitle = json.title;
break;
case "DOMLinkAdded":
// ignore results from subdocuments
if (json.windowId != self.contentWindowId)
return;
let linkType = self._getLinkType(json);
switch(linkType) {
case "icon":
self.loadFavicon(json.href, json.charset);
break;
case "search":
self._searchEngines.push({ title: json.title, href: json.href });
break;
}
break;
case "MozScrolledAreaChanged": {
self._contentDocumentWidth = json.width;
self._contentDocumentHeight = json.height;
self._contentDocumentLeft = (json.left < 0) ? json.left : 0;
// Recalculate whether the visible area is actually in bounds
let view = self.getRootView();
view.scrollBy(0, 0);
break;
}
case "Content:UpdateDisplayPort": {
// Recalculate whether the visible area is actually in bounds
let view = self.getRootView();
view.scrollBy(0, 0);
view._updateCacheViewport();
break;
}
}
}
})
]]></field>
<method name="loadFavicon">
<parameter name="aURL"/>
<parameter name="aCharset"/>
<body><![CDATA[
try { // newURI call is throwing for chrome URI
let iconURI = this._ios.newURI(aURL, aCharset, null);
if (gFaviconService.isFailedFavicon(iconURI))
return;
gFaviconService.setAndFetchFaviconForPage(this.currentURI, iconURI, true,
gFaviconService.FAVICON_LOAD_NON_PRIVATE);
this.mIconURL = iconURI.spec;
} catch (e) {
this.mIconURL = null;
}
]]></body>
</method>
<method name="shouldLoadFavicon">
<body><![CDATA[
let docURI = this.documentURI;
return (docURI && ("schemeIs" in docURI) &&
(docURI.schemeIs("http") || docURI.schemeIs("https")));
]]></body>
</method>
<method name="_getLinkType">
<parameter name="aLink" />
<body><![CDATA[
let type = "";
if (/\bicon\b/i.test(aLink.rel)) {
type = "icon";
}
else if (/\bsearch\b/i.test(aLink.rel) && aLink.type && aLink.title) {
let linkType = aLink.type.replace(/^\s+|\s*(?:;.*)?$/g, "").toLowerCase();
if (linkType == "application/opensearchdescription+xml" && /^(?:https?|ftp):/i.test(aLink.href)) {
type = "search";
}
}
return type;
]]></body>
</method>
<field name="_webProgress"><![CDATA[
({
_browser: this,
_init: function() {
this._browser.messageManager.addMessageListener("Content:StateChange", this);
this._browser.messageManager.addMessageListener("Content:LocationChange", this);
this._browser.messageManager.addMessageListener("Content:SecurityChange", this);
},
receiveMessage: function(aMessage) {
let json = aMessage.json;
switch (aMessage.name) {
case "Content:StateChange":
this._browser.updateWindowId(json.contentWindowId);
break;
case "Content:LocationChange":
try {
let locationURI = this._browser._ios.newURI(json.location, null, null);
this._browser.webNavigation._currentURI = locationURI;
this._browser.webNavigation.canGoBack = json.canGoBack;
this._browser.webNavigation.canGoForward = json.canGoForward;
this._browser._charset = json.charset;
} catch(e) {}
if (this._browser.updateWindowId(json.contentWindowId)) {
this._browser._documentURI = json.documentURI;
this._browser._searchEngines = [];
}
break;
case "Content:SecurityChange":
let serhelper = Components.classes["@mozilla.org/network/serialization-helper;1"]
.getService(Components.interfaces.nsISerializationHelper);
let SSLStatus = json.SSLStatusAsString ? serhelper.deserializeObject(json.SSLStatusAsString) : null;
if (SSLStatus) {
SSLStatus.QueryInterface(Components.interfaces.nsISSLStatus);
// We must check the Extended Validation (EV) state here, on the chrome
// process, because NSS is needed for that determination.
if (SSLStatus && SSLStatus.isExtendedValidation) {
json.state |= Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
}
}
let data = this._getIdentityData(SSLStatus);
this._browser.updateWindowId(json.contentWindowId);
break;
}
},
/**
* Helper to parse out the important parts of the SSL cert for use in constructing
* identity UI strings
*/
_getIdentityData: function(status) {
let result = {};
if (status) {
let cert = status.serverCert;
// Human readable name of Subject
result.subjectOrg = cert.organization;
// SubjectName fields, broken up for individual access
if (cert.subjectName) {
result.subjectNameFields = {};
cert.subjectName.split(",").forEach(function(v) {
var field = v.split("=");
if (field[1])
this[field[0]] = field[1];
}, result.subjectNameFields);
// Call out city, state, and country specifically
result.city = result.subjectNameFields.L;
result.state = result.subjectNameFields.ST;
result.country = result.subjectNameFields.C;
}
// Human readable name of Certificate Authority
result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
if (!this._overrideService)
this._overrideService = Components.classes["@mozilla.org/security/certoverride;1"]
.getService(Components.interfaces.nsICertOverrideService);
// Check whether this site is a security exception.
let currentURI = this._browser.webNavigation._currentURI;
if (currentURI) {
result.isException = this._overrideService.hasMatchingOverride(currentURI.asciiHost, currentURI.port, cert, {}, {});
} else {
result.isException = false;
}
}
return result;
}
})
]]></field>
<property name="webProgress"
readonly="true"
onget="return null"/>
<method name="onPageShow">
<parameter name="aMessage"/>
<body>
<![CDATA[
this.attachFormFill();
if (this.pageReport) {
var json = aMessage.json;
var i = 0;
while (i < this.pageReport.length) {
// Filter out irrelevant reports.
if (this.pageReport[i].requestingWindowId == json.windowId)
i++;
else
this.pageReport.splice(i, 1);
}
if (this.pageReport.length == 0) {
this.pageReport = null;
this.updatePageReport();
}
}
]]>
</body>
</method>
<method name="onPageHide">
<parameter name="aMessage"/>
<body>
<![CDATA[
if (this.pageReport) {
this.pageReport = null;
this.updatePageReport();
}
// Delete the feeds cache if we're hiding the topmost page
// (as opposed to one of its iframes).
if (this.feeds && aMessage.target == this)
this.feeds = null;
this._contentWindowWidth = aMessage.json.contentWindowWidth;
this._contentWindowHeight = aMessage.json.contentWindowHeight;
]]>
</body>
</method>
<method name="onPopupBlocked">
<parameter name="aMessage"/>
<body>
<![CDATA[
if (!this.pageReport) {
this.pageReport = [];
}
let json = aMessage.json;
// XXX Replacing requestingWindow && requestingDocument affects
// http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#500
var obj = {
requestingWindowId: json.windowId,
popupWindowURI: this._ios.newURI(json.popupWindowURI.spec, json.popupWindowURI.charset, null),
popupWindowFeatures: json.popupWindowFeatures,
popupWindowName: json.popupWindowName
};
this.pageReport.push(obj);
this.pageReport.reported = false;
this.updatePageReport();
]]>
</body>
</method>
<field name="_frameLoader">null</field>
<field name="_contentViewManager">null</field>
<!-- Dimensions of content window -->
<field name="_contentWindowWidth">0</field>
<field name="_contentWindowHeight">0</field>
<property name="contentWindowWidth"
onget="return this._contentWindowWidth;"
readonly="true"/>
<property name="contentWindowHeight"
onget="return this._contentWindowHeight;"
readonly="true"/>
<!-- Dimensions of content document -->
<field name="_contentDocumentWidth">0</field>
<field name="_contentDocumentHeight">0</field>
<property name="contentDocumentWidth"
onget="return this._contentDocumentWidth;"
readonly="true"/>
<property name="contentDocumentHeight"
onget="return this._contentDocumentHeight;"
readonly="true"/>
<!-- If this attribute is negative this indicate the document is rtl and
some operations like panning or calculating the cache area should
take it into account. This is useless for non-remote browser -->
<field name="_contentDocumentLeft">0</field>
<!-- The ratio of device pixels to CSS pixels -->
<property name="scale"
onget="return 1;"
onset="return 1;"/>
<!-- These counters are used to update the cached viewport after they reach a certain
threshold when scrolling -->
<field name="_cacheRatioWidth">1</field>
<field name="_cacheRatioHeight">1</field>
<!-- Used in remote tabs only. -->
<method name="_updateCSSViewport">
<body/>
</method>
<!-- Sets size of CSS viewport, which affects how page is layout. -->
<method name="setWindowSize">
<parameter name="width"/>
<parameter name="height"/>
<body>
<![CDATA[
this._contentWindowWidth = width;
this._contentWindowHeight = height;
this.messageManager.sendAsyncMessage("Content:SetWindowSize", {
width: width,
height: height
});
// If the window size is changing, make sure the displayport is in sync
this.getRootView()._updateCacheViewport();
]]>
</body>
</method>
<method name="getRootView">
<body>
<![CDATA[
return this._contentView;
]]>
</body>
</method>
<field name="_contentViewPrototype"><![CDATA[
({
_scrollbox: null,
init: function(aElement) {
this._scrollbox = aElement.scrollBoxObject;
},
isRoot: function() {
return false;
},
scrollBy: function(x, y) {
this._scrollbox.scrollBy(x,y);
},
scrollTo: function(x, y) {
this._scrollbox.scrollTo(x,y);
},
getPosition: function() {
let x = {}, y = {};
this._scrollbox.getPosition(x, y);
return { x: x.value, y: y.value };
}
})
]]>
</field>
<method name="getViewAt">
<parameter name="x"/>
<parameter name="y"/>
<body>
<![CDATA[
let cwu = this.contentDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
let elt = cwu.elementFromPoint(x, y, false, false);
while (elt && !elt.scrollBoxObject)
elt = elt.parentNode;
if (!elt)
return this._contentView;
let cv = Object.create(this._contentViewPrototype);
cv.init(elt);
return cv;
]]>
</body>
</method>
<field name="_contentView"><![CDATA[
({
self: this,
_updateCacheViewport: function() {
},
isRoot: function() {
return true;
},
scrollBy: function(x, y) {
let self = this.self;
self.contentWindow.scrollBy(x, y);
},
scrollTo: function(x, y) {
let self = this.self;
x = Math.floor(Math.max(0, Math.min(self.contentDocumentWidth, x)));
y = Math.floor(Math.max(0, Math.min(self.contentDocumentHeight, y)));
self.contentWindow.scrollTo(x, y);
},
getPosition: function() {
let self = this.self;
let scrollX = {}, scrollY = {};
let cwu = self.contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
cwu.getScrollXY(false, scrollX, scrollY);
return { x: scrollX.value, y: scrollY.value };
},
toString: function() {
return "[View Local]";
}
})
]]>
</field>
<method name="updateWindowId">
<parameter name="aNewId"/>
<body><![CDATA[
if (this.contentWindowId != aNewId) {
this.contentWindowId = aNewId;
return true;
}
return false;
]]></body>
</method>
<field name="_active">false</field>
<property name="active" onget="return this._active;">
<setter><![CDATA[
// Do not change displayport on local tabs!
this._active = val;
this.docShellIsActive = this._active;
]]></setter>
</property>
<!-- Transform the viewport without updating the displayport. -->
<method name="fuzzyZoom">
<parameter name="scale"/>
<parameter name="x"/>
<parameter name="y"/>
<body><![CDATA[
this.getRootView().scrollTo(x, y);
]]></body>
</method>
<!-- After fuzzy zoom, sync the displayport with the new viewport. -->
<method name="finishFuzzyZoom">
<body><![CDATA[
return;
]]></body>
</method>
</implementation>
</binding>
<binding id="remote-browser" extends="#local-browser">
<implementation type="application/javascript" implements="nsIAccessibleProvider, nsIObserver, nsIDOMEventListener, nsIMessageListener">
<property name="accessibleType" readonly="true">
<getter>
<![CDATA[
throw "accessibleType: Supports Remote?";
]]>
</getter>
</property>
<property name="autoscrollEnabled">
<getter>
<![CDATA[
throw "autoscrollEnabled: Supports Remote?";
]]>
</getter>
</property>
<property name="docShell"
readonly="true">
<getter><![CDATA[
return {
forcedCharset : this._charset,
parentCharset : "",
parentCharsetSource : 0
}
]]></getter>
</property>
<field name="_contentTitle">null</field>
<property name="contentTitle"
onget="return this._contentTitle;"
readonly="true"/>
<property name="webNavigation"
onget="return this._remoteWebNavigation;"
readonly="true"/>
<property name="contentWindow"
readonly="true"
onget="return null"/>
<property name="sessionHistory"
onget="return null"
readonly="true"/>
<property name="markupDocumentViewer"
onget="return null"
readonly="true"/>
<property name="contentViewerEdit"
onget="return null"
readonly="true"/>
<property name="contentViewerFile"
onget="return null"
readonly="true"/>
<field name="_charset"></field>
<constructor>
<![CDATA[
this.messageManager.addMessageListener("scroll", this._messageListenerRemote);
]]>
</constructor>
<field name="scrollSync">true</field>
<field name="_messageListenerRemote"><![CDATA[
({
self: this,
receiveMessage: function receiveMessage(aMessage) {
let self = this.self;
let json = aMessage.json;
switch (aMessage.name) {
case "scroll":
if (!self.scrollSync)
return;
this.doScroll(json.x, json.y, 0);
break;
}
},
doScroll: function doScroll(aX, aY, aCount) {
let self = this.self;
// Use floor so that we always guarantee top-left corner of content is visible.
let view = self.getRootView();
view.scrollTo(Math.floor(aX * self.scale), Math.floor(aY * self.scale));
let position = view.getPosition();
if ((position.x != aX * self.scale || position.y != aY * self.scale) && aCount < 3) {
setTimeout((function() {
this.doScroll(aX, aY, ++aCount);
}).bind(this), 0);
}
}
})
]]></field>
<!-- Keep a store of temporary content views. -->
<field name="_contentViews">({})</field>
<!-- There is a point before a page has loaded where a root content view
may not exist. We use this so that we don't have to worry about doing
an if check every time we want to scroll. -->
<field name="_contentNoop"><![CDATA[
({
_updateCacheViewport: function() {},
_getViewportSize: function() {},
isRoot: function() {
return true;
},
_scale: 1,
_setScale: function(scale) {},
scrollBy: function(x, y) {},
scrollTo: function(x, y) {},
getPosition: function() {
return { x: 0, y: 0 };
}
})
]]></field>
<field name="_contentViewPrototype"><![CDATA[
({
self: this,
_id: null,
_contentView: null,
_timeout: null,
_pixelsPannedSinceRefresh: { x: 0, y: 0 },
_lastPanTime: 0,
kDieTime: 3000,
/**
* Die if we haven't panned in a while.
*
* Since we keep a map of active content views, we need to regularly
* check if they are necessary so that every single thing the user
* pans is not kept in memory forever.
*/
_dieIfOld: function() {
if (Date.now() - this._lastPanTime >= this.kDieTime)
this._die();
else
// This doesn't need to be exact, just be sure to clean up at some point.
this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime);
},
/** Cleanup after ourselves. */
_die: function() {
let timeout = this._timeout;
if (timeout) {
clearTimeout(timeout);
this._timeout = null;
}
if (this._contentView && Math.abs(this._pixelsPannedSinceRefresh) > 0)
this._updateCacheViewport();
// We expect contentViews to contain our ID. If not, something bad
// happened.
delete this.self._contentViews[this._id];
},
/**
* Given the cache size and the viewport size, this determines where the cache
* should start relative to the scroll position. This adjusts the position based
* on which direction the user is panning, so that we use our cache as
* effectively as possible.
*
* @param aDirection Negative means user is panning to the left or above
* Zero means user did not pan
* Positive means user is panning to the right or below
* @param aViewportSize The width or height of the viewport
* @param aCacheSize The width or height of the displayport
*/
_getRelativeCacheStart: function(aDirection, aViewportSize, aCacheSize) {
// Remember that this is relative to the viewport scroll position.
// Let's assume we are thinking about the y-axis.
// The extreme cases:
// |0| would mean that there is no content available above
// |aViewportSize - aCacheSize| would mean no content available below
//
// Taking the average of the extremes puts equal amounts of cache on the
// top and bottom of the viewport. If we think of this like a weighted
// average, .5 is the sweet spot where equals amounts of content are
// above and below the visible area.
//
// This weight is therefore how much of the cache is above (or to the
// left) the visible area.
let cachedAbove = .5;
// If panning down, leave only 25% of the non-visible cache above.
if (aDirection > 0)
cachedAbove = .25;
// If panning up, Leave 75% of the non-visible cache above.
if (aDirection < 0)
cachedAbove = .75;
return (aViewportSize - aCacheSize) * cachedAbove;
},
/** Determine size of the pixel cache. */
_getCacheSize: function(viewportSize) {
let self = this.self;
let contentView = this._contentView;
let cacheWidth = self._cacheRatioWidth * viewportSize.width;
let cacheHeight = self._cacheRatioHeight * viewportSize.height;
let contentSize = this._getContentSize();
let contentWidth = contentSize.width;
let contentHeight = contentSize.height;
// There are common cases, such as long skinny pages, where our cache size is
// bigger than our content size. In those cases, we take that sliver of leftover
// space and apply it to the other dimension.
if (contentWidth < cacheWidth) {
cacheHeight += (cacheWidth - contentWidth) * cacheHeight / cacheWidth;
cacheWidth = contentWidth;
} else if (contentHeight < cacheHeight) {
cacheWidth += (cacheHeight - contentHeight) * cacheWidth / cacheHeight;
cacheHeight = contentHeight;
}
return { width: cacheWidth, height: cacheHeight };
},
_sendDisplayportUpdate: function(scrollX, scrollY) {
let self = this.self;
if (!self.active)
return;
let contentView = this._contentView;
let viewportSize = this._getViewportSize();
let cacheSize = this._getCacheSize(viewportSize);
let cacheX = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.x, viewportSize.width, cacheSize.width) + contentView.scrollX;
let cacheY = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.y, viewportSize.height, cacheSize.height) + contentView.scrollY;
let contentSize = this._getContentSize();
// Use our pixels efficiently and don't try to cache things outside of content
// boundaries (The left bound can be negative because of RTL).
let rootScale = self.scale;
let leftBound = self._contentDocumentLeft * rootScale;
let bounds = new Rect(leftBound, 0, contentSize.width, contentSize.height);
let displayport = new Rect(cacheX, cacheY, cacheSize.width, cacheSize.height);
displayport.translateInside(bounds);
self.messageManager.sendAsyncMessage("Content:SetCacheViewport", {
scrollX: Math.round(scrollX) / rootScale,
scrollY: Math.round(scrollY) / rootScale,
x: Math.round(displayport.x) / rootScale,
y: Math.round(displayport.y) / rootScale,
w: Math.round(displayport.width) / rootScale,
h: Math.round(displayport.height) / rootScale,
scale: rootScale,
id: contentView.id
});
this._pixelsPannedSinceRefresh.x = 0;
this._pixelsPannedSinceRefresh.y = 0;
},
_updateCSSViewport: function() {
let contentView = this._contentView;
this._sendDisplayportUpdate(contentView.scrollX,
contentView.scrollY);
},
/**
* The cache viewport is what parts of content is cached in the parent process for
* fast scrolling. This syncs that up with the current projection viewport.
*/
_updateCacheViewport: function() {
// Do not update scroll values for content.
if (this.isRoot())
this._sendDisplayportUpdate(-1, -1);
else {
let contentView = this._contentView;
this._sendDisplayportUpdate(contentView.scrollX,
contentView.scrollY);
}
},
_getContentSize: function() {
let self = this.self;
return { width: this._contentView.contentWidth,
height: this._contentView.contentHeight };
},
_getViewportSize: function() {
let self = this.self;
if (this.isRoot()) {
let bcr = self.getBoundingClientRect();
return { width: bcr.width, height: bcr.height };
} else {
return { width: this._contentView.viewportWidth,
height: this._contentView.viewportHeight };
}
},
init: function(contentView) {
let self = this.self;
this._contentView = contentView;
this._id = contentView.id;
this._scale = 1;
self._contentViews[this._id] = this;
if (!this.isRoot()) {
// Non-root content views are short lived.
this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime);
// This iframe may not have a display port yet, so build up a cache
// immediately.
this._updateCacheViewport();
}
},
isRoot: function() {
return this.self._contentViewManager.rootContentView == this._contentView;
},
scrollBy: function(x, y) {
let self = this.self;
// Bounding content rectangle is in device pixels
let contentView = this._contentView;
let viewportSize = this._getViewportSize();
let contentSize = this._getContentSize();
// Calculate document dimensions in device pixels
let scrollRangeX = contentSize.width - viewportSize.width;
let scrollRangeY = contentSize.height - viewportSize.height;
let leftOffset = self._contentDocumentLeft * this._scale;
x = Math.floor(Math.max(leftOffset, Math.min(scrollRangeX + leftOffset, contentView.scrollX + x))) - contentView.scrollX;
y = Math.floor(Math.max(0, Math.min(scrollRangeY, contentView.scrollY + y))) - contentView.scrollY;
if (x == 0 && y == 0)
return;
contentView.scrollBy(x, y);
this._lastPanTime = Date.now();
this._pixelsPannedSinceRefresh.x += x;
this._pixelsPannedSinceRefresh.y += y;
if (Math.abs(this._pixelsPannedSinceRefresh.x) > 20 ||
Math.abs(this._pixelsPannedSinceRefresh.y) > 20)
this._updateCacheViewport();
},
scrollTo: function(x, y) {
let contentView = this._contentView;
this.scrollBy(x - contentView.scrollX, y - contentView.scrollY);
},
_setScale: function _setScale(scale) {
this._scale = scale;
this._contentView.setScale(scale, scale);
},
getPosition: function() {
let contentView = this._contentView;
return { x: contentView.scrollX, y: contentView.scrollY };
}
})
]]>
</field>
<!-- Transform the viewport without updating the displayport. -->
<method name="fuzzyZoom">
<parameter name="scale"/>
<parameter name="x"/>
<parameter name="y"/>
<body><![CDATA[
let rootView = this.getRootView();
rootView._setScale(scale);
if ("_contentView" in rootView)
rootView._contentView.scrollTo(x, y);
]]></body>
</method>
<!-- After fuzzy zoom, sync the displayport with the new viewport. -->
<method name="finishFuzzyZoom">
<body><![CDATA[
let view = this.getRootView();
// ensure that we are scrolled within the page's viewable area
view.scrollBy(0,0);
view._updateCacheViewport();
let event = document.createEvent("Events");
event.initEvent("ZoomChanged", true, false);
this.dispatchEvent(event);
]]></body>
</method>
<!-- The ratio of CSS pixels to device pixels. -->
<property name="scale">
<getter><![CDATA[
return this.getRootView()._scale;
]]></getter>
<setter><![CDATA[
if (val <= 0 || val == this.scale)
return;
let rootView = this.getRootView();
rootView._setScale(val);
this.finishFuzzyZoom();
return val;
]]></setter>
</property>
<method name="_getView">
<parameter name="contentView"/>
<body>
<![CDATA[
if (!contentView) return null;
// See if we have cached it.
let id = contentView.id;
let jsContentView = this._contentViews[id];
if (jsContentView) {
// Content view may have changed if it became inactive for a
// little while.
jsContentView._contentView = contentView;
return jsContentView;
}
// Not cached. Create it.
jsContentView = Object.create(this._contentViewPrototype);
jsContentView.init(contentView);
return jsContentView;
]]>
</body>
</method>
<!-- Get root content view. -->
<method name="getRootView">
<body>
<![CDATA[
let contentView = this._contentViewManager.rootContentView;
return this._getView(contentView) || this._contentNoop;
]]>
</body>
</method>
<!-- Get contentView for position (x, y) relative to the browser element -->
<method name="getViewAt">
<parameter name="x"/>
<parameter name="y"/>
<body>
<![CDATA[
let manager = this._contentViewManager;
let contentView = manager.getContentViewsIn(x, y, 0, 0, 0, 0)[0] ||
manager.rootContentView;
return this._getView(contentView);
]]>
</body>
</method>
<!-- Synchronize the CSS viewport with the projection viewport. -->
<method name="_updateCSSViewport">
<body>
<![CDATA[
let rootView = this.getRootView();
rootView._updateCSSViewport();
]]>
</body>
</method>
<property name="active" onget="return this._active;">
<setter><![CDATA[
this._active = val;
let keepVisible = false;
this.messageManager.sendAsyncMessage((val ? "Content:Activate" : "Content:Deactivate"), { keepviewport: keepVisible });
if (val)
this.getRootView()._updateCacheViewport();
]]></setter>
</property>
</implementation>
</binding>
</bindings>