Repository URL to install this package:
|
Version:
3.5.361976.0301 ▾
|
Ø ¦1 §1& ¨1! ©1?% ª1º7 «1R ¬1[ 15 ®1 ¯1 °1 ±1 ²1´ ³1} ´1h# µ1ì* ¶164 ·18 ¸1«V ¹1æe º1Þp »1§v ¼1' ½1 ¾1Ǎ ¿1¢ À1(³ Á1ḠÂ1ǻ Ã1ª¾ Ä1ÑÅ Å1bÉ Æ11Ø Ç1íë È1êü É1Éÿ Ê1 Ë1 Ì1× Í1T Î1% Ï1¡- Ð1Õ4 Ñ1{7 Ò1Ç< ö1I ÷1'J ²>K ³>M ´>vP µ>%Q ¶>xS º>ñU »> V ¼>OW ½>þW ¾>X ¿>\Y À>Z Á>ºZ Â>i[ Ã>m\ Ç>s] É>t^ Ê>` Ë>a Ì>zj ÔbSl Õbn Öb o ×bwq ØbÚu ÙbÚz Úb} Ûb6 Übê Ýb
Þb¨ ßbµ àb áb âb ãb` äb¢ åbJ² æb.· çb¹ èb½ ébÀ êbqà ëbjÆ ìbÆÆ íb]È ègõÉ ég+Ó êg(Õ ëgcØ ìgÝ ígýæ îg¡ç ïgÜê ðgî ñgó òggú ógý ôg õgú ögÙ ÷gù øg ùg úgé ûg5" ügp& ýg) þgx- ÿg0 hw4 h²7 hZ: hIC hL hñR hjX hoZ hW[ hÇ\
h°^ hÖa h;b
h«c he hýf hsi h|j húk hm h"o hp hr h®s h@u h²x hAz h¾{ hP} hÍ~ ha h6 h¦ !hz "h #h¡ $hҙ %hx &hT¦ 'hF© (hª )h¯ *hµ +hqµ ,híµ -hm¶ .h.· /h¸ 0h¸ 1h
¹ 2ho¹ 3hö¹ 4hº 5hW» 6h,¼ 7hû¼ 8hའ9hӾ :h$ ;h <h
à =h¶Ã >hÄ ?h`Å @h5Æ Ah
Ç BhßÇ Ch´È DhÊ EhrÌ FhQÎ Gh&Ï HhûÏ IhÐÐ Jh KhR Lh Mhà NhE Shà Th+ oh¾ ph- qh rh
wh
xh; yh zhÏ {h
|hM }h ~hÊ h hN h h9 h/ h& £ <!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<script>
function setMessage(msg) {
document.getElementById('message').textContent = msg;
}
function notifyDidFinishLoading() {
if (plugin.didFinishLoading)
plugin.didFinishLoading();
if (plugin.didFinishIconRepositionForTesting)
plugin.didFinishIconRepositionForTesting();
}
</script>
<style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
html, body {
-webkit-user-select: none;
font-family: sans-serif;
height: 100%;
margin: 0;
overflow: hidden;
text-align: center;
width: 100%;
}
h1 {
display: none;
font-size: 10pt;
font-weight: normal;
padding: 0pt 10pt;
}
p {
font-size: 8pt;
padding: 0pt 14pt;
}
#t {
background-color: #f7f7f7;
color: #646464;
}
#outer {
align-items: center;
box-sizing: border-box;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
flex-direction: column;
}
.icon {
max-height: 100%;
max-width: 100%;
opacity: .3;
}
@media (orientation: landscape) and (min-height: 2em) and (min-width: 14em) {
#outer {
flex-direction: row;
}
.icon {
max-height: 100%;
max-width: 50%;
}
h1 {
display: block;
}
}
@media (min-height: 7em) and (min-width: 6em) {
#outer {
flex-direction: column;
}
.icon {
max-height: 50%;
max-width: 100%;
}
h1 {
display: block;
}
}
</style>
</head>
<body id="t" onload="notifyDidFinishLoading();">
<div i18n-values="title:name" id="outer">
<img class="icon"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0iIzYyNjI2MiI+CiAgICA8cGF0aCBkPSJNMCAwaDQ4djQ4SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNNDEgMjJoLTN2LThjMC0yLjIxLTEuNzktNC00LTRoLThWN2MwLTIuNzYtMi4yNC01LTUtNXMtNSAyLjI0LTUgNXYzSDhjLTIuMjEgMC0zLjk4IDEuNzktMy45OCA0bC0uMDEgNy42SDdjMi45OCAwIDUuNCAyLjQyIDUuNCA1LjRTOS45OCAzMi40IDcgMzIuNEg0LjAxTDQgNDBjMCAyLjIxIDEuNzkgNCA0IDRoNy42di0zYzAtMi45OCAyLjQyLTUuNCA1LjQtNS40IDIuOTggMCA1LjQgMi40MiA1LjQgNS40djNIMzRjMi4yMSAwIDQtMS43OSA0LTR2LThoM2MyLjc2IDAgNS0yLjI0IDUtNXMtMi4yNC01LTUtNXoiLz4KPC9zdmc+Cg==">
<h1 id="message" i18n-content="message"></h1>
</div>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<script>
function insertLink() {
// Replace the chrome://plugins text with a working link (i18n_template
// doesn't allow raw HTML in template data).
var link = document.getElementById("enable_link");
var link_html = link.innerHTML;
link.parentNode.removeChild(link);
var message = document.getElementById("message");
var message_html = message.innerHTML;
message.innerHTML = message_html.replace('chrome://plugins', link_html);
}
</script>
<style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
html, body {
-webkit-user-select: none;
font-family: sans-serif;
height: 100%;
margin: 0;
overflow: hidden;
text-align: center;
width: 100%;
}
h1 {
display: none;
font-size: 10pt;
font-weight: normal;
padding: 0pt 10pt;
}
p {
font-size: 8pt;
padding: 0pt 14pt;
}
#t {
background-color: #f7f7f7;
color: #646464;
}
#outer {
align-items: center;
box-sizing: border-box;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
flex-direction: column;
}
.icon {
max-height: 100%;
max-width: 100%;
opacity: .3;
}
@media (orientation: landscape) and (min-height: 2em) and (min-width: 14em) {
#outer {
flex-direction: row;
}
.icon {
max-height: 100%;
max-width: 50%;
}
h1 {
display: block;
}
}
@media (min-height: 7em) and (min-width: 6em) {
#outer {
flex-direction: column;
}
.icon {
max-height: 50%;
max-width: 100%;
}
h1 {
display: block;
}
}
</style>
</head>
<body id="t" onLoad="insertLink()">
<div i18n-values="title:name" id="outer">
<img class="icon"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0iIzYyNjI2MiI+CiAgICA8cGF0aCBkPSJNMCAwaDQ4djQ4SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNNDEgMjJoLTN2LThjMC0yLjIxLTEuNzktNC00LTRoLThWN2MwLTIuNzYtMi4yNC01LTUtNXMtNSAyLjI0LTUgNXYzSDhjLTIuMjEgMC0zLjk4IDEuNzktMy45OCA0bC0uMDEgNy42SDdjMi45OCAwIDUuNCAyLjQyIDUuNCA1LjRTOS45OCAzMi40IDcgMzIuNEg0LjAxTDQgNDBjMCAyLjIxIDEuNzkgNCA0IDRoNy42di0zYzAtMi45OCAyLjQyLTUuNCA1LjQtNS40IDIuOTggMCA1LjQgMi40MiA1LjQgNS40djNIMzRjMi4yMSAwIDQtMS43OSA0LTR2LThoM2MyLjc2IDAgNS0yLjI0IDUtNXMtMi4yNC01LTUtNXoiLz4KPC9zdmc+Cg==">
<h1 id="message" i18n-content="message"></h1>
<div id="enable_link">
<a href="#" onclick="plugin.openAboutPlugins();">chrome://plugins</a>
</div>
</div>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<script>
function setMessage(msg) {
document.getElementById('message').textContent = msg;
}
function notifyDidFinishLoading() {
if (plugin.didFinishLoading)
plugin.didFinishLoading();
if (plugin.didFinishIconRepositionForTesting)
plugin.didFinishIconRepositionForTesting();
}
</script>
<style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
html, body {
-webkit-user-select: none;
font-family: sans-serif;
height: 100%;
margin: 0;
overflow: hidden;
text-align: center;
width: 100%;
}
h1 {
display: none;
font-size: 10pt;
font-weight: normal;
padding: 0pt 10pt;
}
p {
font-size: 8pt;
padding: 0pt 14pt;
}
#t {
background-color: #f7f7f7;
color: #646464;
}
#outer {
align-items: center;
box-sizing: border-box;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
flex-direction: column;
}
.icon {
max-height: 100%;
max-width: 100%;
opacity: .3;
}
@media (orientation: landscape) and (min-height: 2em) and (min-width: 14em) {
#outer {
flex-direction: row;
}
.icon {
max-height: 100%;
max-width: 50%;
}
h1 {
display: block;
}
}
@media (min-height: 7em) and (min-width: 6em) {
#outer {
flex-direction: column;
}
.icon {
max-height: 50%;
max-width: 100%;
}
h1 {
display: block;
}
}
</style>
<style>
#outer {
cursor: pointer;
}
</style>
</head>
<body id="t" onload="notifyDidFinishLoading();">
<div i18n-values="title:name" id="outer">
<img class="icon"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OHB4IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0iIzYyNjI2MiI+CiAgICA8cGF0aCBkPSJNMCAwaDQ4djQ4SDB6IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBkPSJNNDEgMjJoLTN2LThjMC0yLjIxLTEuNzktNC00LTRoLThWN2MwLTIuNzYtMi4yNC01LTUtNXMtNSAyLjI0LTUgNXYzSDhjLTIuMjEgMC0zLjk4IDEuNzktMy45OCA0bC0uMDEgNy42SDdjMi45OCAwIDUuNCAyLjQyIDUuNCA1LjRTOS45OCAzMi40IDcgMzIuNEg0LjAxTDQgNDBjMCAyLjIxIDEuNzkgNCA0IDRoNy42di0zYzAtMi45OCAyLjQyLTUuNCA1LjQtNS40IDIuOTggMCA1LjQgMi40MiA1LjQgNS40djNIMzRjMi4yMSAwIDQtMS43OSA0LTR2LThoM2MyLjc2IDAgNS0yLjI0IDUtNXMtMi4yNC01LTUtNXoiLz4KPC9zdmc+Cg==">
<h1 id="message" i18n-content="message"></h1>
</div>
<script>
window.onkeydown = function(e) {
if (e.key == 'Enter' || e.key == ' ') {
plugin.showPermissionBubble();
e.preventDefault();
}
};
document.getElementById('outer').onclick = function() {
plugin.showPermissionBubble();
};
</script>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<script>
window.onload = function() {
if (plugin.didFinishLoading)
plugin.didFinishLoading();
};
window.onkeydown = function(e) {
if (e.key == 'Enter' || e.key == ' ') {
plugin.load();
e.preventDefault();
}
};
</script>
<style>/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
html, body {
-webkit-user-select: none;
font-family: sans-serif;
height: 100%;
margin: 0;
overflow: hidden;
text-align: center;
width: 100%;
}
h1 {
display: none;
font-size: 10pt;
font-weight: normal;
padding: 0pt 10pt;
}
p {
font-size: 8pt;
padding: 0pt 14pt;
}
#t {
background-color: #f7f7f7;
color: #646464;
}
#outer {
align-items: center;
box-sizing: border-box;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
flex-direction: column;
}
.icon {
max-height: 100%;
max-width: 100%;
opacity: .3;
}
@media (orientation: landscape) and (min-height: 2em) and (min-width: 14em) {
#outer {
flex-direction: row;
}
.icon {
max-height: 100%;
max-width: 50%;
}
h1 {
display: block;
}
}
@media (min-height: 7em) and (min-width: 6em) {
#outer {
flex-direction: column;
}
.icon {
max-height: 50%;
max-width: 100%;
}
h1 {
display: block;
}
}
</style>
<style>
#outer {
border: none;
flex-direction: row;
cursor: pointer;
}
#shielding {
background-color: rgba(0, 0, 0, 0.5);
height: 100%;
left: 0px;
position: absolute;
top: 0px;
width: 100%;
z-index: 2;
}
#plugin-icon {
opacity: 0.8;
max-height: 100%;
max-width: 100%;
min-width: 0;
min-height: 0;
}
#plugin-icon:hover {
opacity: 0.95;
}
#poster {
height: 100%;
object-fit: contain;
width: 100%;
z-index: 1;
}
#inner-container {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
left: 0px;
max-height: 100%;
max-width: 100%;
position: absolute;
top: 0px;
width: 100%;
z-index: 2;
}
</style>
<base i18n-values="href:baseurl">
</head>
<body>
<div i18n-values="title:name" id="outer">
<img id="poster" i18n-values="srcset:poster">
<div id="shielding"></div>
<div id="inner-container"
i18n-values=".style.width:visibleWidth;.style.height:visibleHeight">
<img id="plugin-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAA8dJREFUaAXdWs9vEkEUZkvZQloNxJoY40kxJsDNo9GDif+Bf4CHpjEmJPoPeMce1F408dYjQePBgycv3vRG4GBMPREPLSlpCUWggN+3ncVl6PJrZ5a6k0x2Z2fnve/befPmzcwaIbVpGeLCyEsiG5L4Pso9kbu4nkj1CysSaAQ5trm5uV6pVB4eHx+/brVan3u93s9+v19Fbolc5TPW8R2+yzZsK2TIpPFYf6LS6Pb29rX9/f0NgPsIsA3kWVODbSmDsigT2TdCZjabvVyv158D9eGsyMe8f0iZlA0yJrK2FE4mkxer1eoTmMjvMYA8VVE2dVAXmHC8KU1mPp9Pdjqd755QztCYuqgTLJT1TrRYLN7T2Qtu/KiTukGGY8dTiu3t7T2CoqabMh+eN4kBLOjd5kpRQcIHrJNVCDIz94zJLoX4RfaEzK4pzGzqMRPmIFvEmJCRy2ViEg5gsjej2/PTO8lgJ5WJTbjmsePFpA+fJGzR9cQIFq4mZnBWPY8mJX84YhQRwJnhTFSEHXK7mcrNZvMdGjBQ1JqIFb0y4sUMBm3Q7Dl2KpVKd5jx1Xa1MgFWEWgO9UqEEagKxSSBL7WSy+WuIrJ9r0Kmmwxihi4uIwYpJkJxtzZTPxdE7IXVaq1We4bGWkyNmMHAmvGpMIQFzqppmg8GtNTccCXYSCQSb8rl8n2Q+aVG7D8pxEzs9pNlrtam/uQTXnT0iC2f14guUyN2yOcSO7TCpecEfFNXuxChHvY+Te0phCkzNWInB8uWw+HwLWrSnGxTe6vS1AT2JYtIJBK5oZmEU/yfTCbzbWtr62673f7grJjnXmC3xvoaupq7HUrSGNOScaoytSoEr1lMcHNB1uJDWZWpWdhtIj7g1qvCcltQUUe+pFfViHR+xBi82EY8Hn+Be9dodqTl8ANit1wix0ZtuE57ifPKFczMOyDxCtrmJRES2PvskR4WK7uYJZPa4Z8qiMIh3E6lUjuGYVz3qpPYyYHd2+t2uz+8CpyiPXVxQnycTqe/qCBBnQK7RaR7cHDwdQogXl5xmtJLCJrblGQQAjt39q2gcR22Ns8mNMfXUDpjHqEp6VqfNMSO/oCfzjBeaWzl/GojYTzonBwdHX0a0PJ+o82UnNAE5qHDosAsdUk0EJsPJBKY7SCSCcQGHYmEgrJlSi7B2MQmE6RAHCucUoEXC8JBj00mEEdvNplAHIbaZMxCoXDTzwMg6lJ9PG2TCcQPAzYZXv/7XzicZALxU41M6Fz85jR02uNEOOc9NzN4dMz1ObMsX9uPZ38BvwZw6z92PI4AAAAASUVORK5CYII=" />
</div>
</div>
<script>
document.getElementById('poster').onerror = function() {
this.hidden = true;
};
document.getElementById('outer').onclick = function() {
plugin.load();
};
window.resizePoster = function(marginLeft, marginTop, width, height) {
var container = document.getElementById('inner-container');
container.style.marginLeft = marginLeft;
container.style.marginTop = marginTop;
container.style.width = width;
container.style.height = height;
if (plugin.didFinishIconRepositionForTesting) {
// Defer until reflow complete.
window.setTimeout(function() {
plugin.didFinishIconRepositionForTesting();
});
}
};
</script>
</body>
</html>
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var chrome;
if (!chrome)
chrome = {};
if (!chrome.embeddedSearch) {
chrome.embeddedSearch = new function() {
this.searchBox = new function() {
// =======================================================================
// Private functions
// =======================================================================
native function GetMostVisitedItemData();
native function GetQuery();
native function GetSearchRequestParams();
native function GetRightToLeft();
native function GetSuggestionToPrefetch();
native function IsFocused();
native function IsKeyCaptureEnabled();
native function Paste();
native function StartCapturingKeyStrokes();
native function StopCapturingKeyStrokes();
// =======================================================================
// Exported functions
// =======================================================================
this.__defineGetter__('isFocused', IsFocused);
this.__defineGetter__('isKeyCaptureEnabled', IsKeyCaptureEnabled);
this.__defineGetter__('rtl', GetRightToLeft);
this.__defineGetter__('suggestion', GetSuggestionToPrefetch);
this.__defineGetter__('value', GetQuery);
Object.defineProperty(this, 'requestParams',
{ get: GetSearchRequestParams });
// This method is restricted to chrome-search://most-visited pages by
// checking the invoking context's origin in searchbox_extension.cc.
// TODO(treib): Why is this in searchBox rather than newTabPage?
this.getMostVisitedItemData = function(restrictedId) {
var item = GetMostVisitedItemData(restrictedId);
if (item) {
var sizeInPx = Math.floor(48 * window.devicePixelRatio + 0.5);
// Populate large icon and fallback icon data, if they exist. We'll
// render everything here, once these become available by default.
if (item.largeIconUrl) {
item.largeIconUrl +=
sizeInPx + "/" + item.renderViewId + "/" + item.rid;
}
if (item.fallbackIconUrl) {
item.fallbackIconUrl +=
sizeInPx + ",,,,/" + item.renderViewId + "/" + item.rid;
}
}
return item;
};
this.paste = function(value) {
Paste(value);
};
this.startCapturingKeyStrokes = function() {
StartCapturingKeyStrokes();
};
this.stopCapturingKeyStrokes = function() {
StopCapturingKeyStrokes();
};
this.onfocuschange = null;
this.onkeycapturechange = null;
this.onsubmit = null;
this.onsuggestionchange = null;
};
this.newTabPage = new function() {
// =======================================================================
// Private functions
// =======================================================================
native function CheckIsUserSignedInToChromeAs();
native function CheckIsUserSyncingHistory();
native function DeleteMostVisitedItem();
native function GetMostVisitedItems();
native function GetThemeBackgroundInfo();
native function IsInputInProgress();
native function LogEvent();
native function LogMostVisitedImpression();
native function LogMostVisitedNavigation();
native function UndoAllMostVisitedDeletions();
native function UndoMostVisitedDeletion();
function GetMostVisitedItemsWrapper() {
var mostVisitedItems = GetMostVisitedItems();
for (var i = 0, item; item = mostVisitedItems[i]; ++i) {
item.faviconUrl = GenerateFaviconURL(item.renderViewId, item.rid);
// These properties are private data and should not be returned to
// the page. They are only accessible via getMostVisitedItemData().
delete item.url;
delete item.title;
delete item.domain;
delete item.direction;
delete item.renderViewId;
delete item.largeIconUrl;
delete item.fallbackIconUrl;
}
return mostVisitedItems;
}
function GenerateFaviconURL(renderViewId, rid) {
return "chrome-search://favicon/size/16@" +
window.devicePixelRatio + "x/" +
renderViewId + "/" + rid;
}
// =======================================================================
// Exported functions
// =======================================================================
this.__defineGetter__('isInputInProgress', IsInputInProgress);
this.__defineGetter__('mostVisited', GetMostVisitedItemsWrapper);
this.__defineGetter__('themeBackgroundInfo', GetThemeBackgroundInfo);
this.deleteMostVisitedItem = function(restrictedId) {
DeleteMostVisitedItem(restrictedId);
};
this.checkIsUserSignedIntoChromeAs = function(identity) {
CheckIsUserSignedInToChromeAs(identity);
};
this.checkIsUserSyncingHistory = function() {
CheckIsUserSyncingHistory();
};
// This method is restricted to chrome-search://most-visited pages by
// checking the invoking context's origin in searchbox_extension.cc.
this.logEvent = function(histogram_name) {
LogEvent(histogram_name);
};
// This method is restricted to chrome-search://most-visited pages by
// checking the invoking context's origin in searchbox_extension.cc.
this.logMostVisitedImpression = function(position, provider) {
LogMostVisitedImpression(position, provider);
};
// This method is restricted to chrome-search://most-visited pages by
// checking the invoking context's origin in searchbox_extension.cc.
this.logMostVisitedNavigation = function(position, provider) {
LogMostVisitedNavigation(position, provider);
};
this.undoAllMostVisitedDeletions = function() {
UndoAllMostVisitedDeletions();
};
this.undoMostVisitedDeletion = function(restrictedId) {
UndoMostVisitedDeletion(restrictedId);
};
this.onsignedincheckdone = null;
this.onhistorysynccheckdone = null;
this.oninputcancel = null;
this.oninputstart = null;
this.onmostvisitedchange = null;
this.onthemechange = null;
};
// TODO(jered): Remove when google no longer expects this object.
chrome.searchBox = this.searchBox;
};
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the app API.
var GetAvailability = requireNative('v8_context').GetAvailability;
if (!GetAvailability('app').is_available) {
exports.$set('binding', {});
exports.$set('onInstallStateResponse', function(){});
return;
}
var appNatives = requireNative('app');
var process = requireNative('process');
var extensionId = process.GetExtensionId();
var logActivity = requireNative('activityLogger');
function wrapForLogging(fun) {
if (!extensionId)
return fun; // nothing interesting to log without an extension
return function() {
// TODO(ataly): We need to make sure we use the right prototype for
// fun.apply. Array slice can either be rewritten or similarly defined.
logActivity.LogAPICall(extensionId, "app." + fun.name,
$Array.slice(arguments));
return $Function.apply(fun, this, arguments);
};
}
// This becomes chrome.app
var app = {
getIsInstalled: wrapForLogging(appNatives.GetIsInstalled),
getDetails: wrapForLogging(appNatives.GetDetails),
runningState: wrapForLogging(appNatives.GetRunningState)
};
// Tricky; "getIsInstalled" is actually exposed as the getter "isInstalled",
// but we don't have a way to express this in the schema JSON (nor is it
// worth it for this one special case).
//
// So, define it manually, and let the getIsInstalled function act as its
// documentation.
app.__defineGetter__('isInstalled', wrapForLogging(appNatives.GetIsInstalled));
// Called by app_bindings.cc.
function onInstallStateResponse(state, callbackId) {
var callback = callbacks[callbackId];
delete callbacks[callbackId];
if (typeof(callback) == 'function') {
try {
callback(state);
} catch (e) {
console.error('Exception in chrome.app.installState response handler: ' +
e.stack);
}
}
}
// TODO(kalman): move this stuff to its own custom bindings.
var callbacks = {};
var nextCallbackId = 1;
app.installState = function getInstallState(callback) {
var callbackId = nextCallbackId++;
callbacks[callbackId] = callback;
appNatives.GetInstallState(callbackId);
};
if (extensionId)
app.installState = wrapForLogging(app.installState);
exports.$set('binding', app);
exports.$set('onInstallStateResponse', onInstallStateResponse);
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom bindings for the automation API.
var AutomationNode = require('automationNode').AutomationNode;
var AutomationRootNode = require('automationNode').AutomationRootNode;
var automation = require('binding').Binding.create('automation');
var automationInternal =
require('binding').Binding.create('automationInternal').generate();
var eventBindings = require('event_bindings');
var Event = eventBindings.Event;
var exceptionHandler = require('uncaught_exception_handler');
var forEach = require('utils').forEach;
var lastError = require('lastError');
var logging = requireNative('logging');
var nativeAutomationInternal = requireNative('automationInternal');
var GetRoutingID = nativeAutomationInternal.GetRoutingID;
var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions;
var DestroyAccessibilityTree =
nativeAutomationInternal.DestroyAccessibilityTree;
var GetIntAttribute = nativeAutomationInternal.GetIntAttribute;
var StartCachingAccessibilityTrees =
nativeAutomationInternal.StartCachingAccessibilityTrees;
var AddTreeChangeObserver = nativeAutomationInternal.AddTreeChangeObserver;
var RemoveTreeChangeObserver =
nativeAutomationInternal.RemoveTreeChangeObserver;
var GetFocusNative = nativeAutomationInternal.GetFocus;
var schema = GetSchemaAdditions();
/**
* A namespace to export utility functions to other files in automation.
*/
window.automationUtil = function() {};
// TODO(aboxhall): Look into using WeakMap
var idToCallback = {};
var DESKTOP_TREE_ID = 0;
automationUtil.storeTreeCallback = function(id, callback) {
if (!callback)
return;
var targetTree = AutomationRootNode.get(id);
if (!targetTree) {
// If we haven't cached the tree, hold the callback until the tree is
// populated by the initial onAccessibilityEvent call.
if (id in idToCallback)
idToCallback[id].push(callback);
else
idToCallback[id] = [callback];
} else {
callback(targetTree);
}
};
/**
* Global list of tree change observers.
* @type {Object<number, TreeChangeObserver>}
*/
automationUtil.treeChangeObserverMap = {};
/**
* The id of the next tree change observer.
* @type {number}
*/
automationUtil.nextTreeChangeObserverId = 1;
/**
* @type {AutomationNode} The current focused node. This is only updated
* when calling automationUtil.updateFocusedNode.
*/
automationUtil.focusedNode = null;
/**
* Gets the currently focused AutomationNode.
* @return {AutomationNode}
*/
automationUtil.getFocus = function() {
var focusedNodeInfo = GetFocusNative(DESKTOP_TREE_ID);
if (!focusedNodeInfo)
return null;
var tree = AutomationRootNode.getOrCreate(focusedNodeInfo.treeId);
if (tree)
return privates(tree).impl.get(focusedNodeInfo.nodeId);
};
/**
* Update automationUtil.focusedNode to be the node that currently has focus.
*/
automationUtil.updateFocusedNode = function() {
automationUtil.focusedNode = automationUtil.getFocus();
};
automation.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
// TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) {
var routingID = GetRoutingID();
StartCachingAccessibilityTrees();
// enableTab() ensures the renderer for the active or specified tab has
// accessibility enabled, and fetches its ax tree id to use as
// a key in the idToAutomationRootNode map. The callback to
// enableTab is bound to the callback passed in to getTree(), so that once
// the tree is available (either due to having been cached earlier, or after
// an accessibility event occurs which causes the tree to be populated), the
// callback can be called.
var params = { routingID: routingID, tabID: tabID };
automationInternal.enableTab(params,
function onEnable(id) {
if (lastError.hasError(chrome)) {
callback();
return;
}
automationUtil.storeTreeCallback(id, callback);
});
});
var desktopTree = null;
apiFunctions.setHandleRequest('getDesktop', function(callback) {
StartCachingAccessibilityTrees();
desktopTree = AutomationRootNode.get(DESKTOP_TREE_ID);
if (!desktopTree) {
if (DESKTOP_TREE_ID in idToCallback)
idToCallback[DESKTOP_TREE_ID].push(callback);
else
idToCallback[DESKTOP_TREE_ID] = [callback];
var routingID = GetRoutingID();
// TODO(dtseng): Disable desktop tree once desktop object goes out of
// scope.
automationInternal.enableDesktop(routingID, function() {
if (lastError.hasError(chrome)) {
AutomationRootNode.destroy(DESKTOP_TREE_ID);
callback();
return;
}
});
} else {
callback(desktopTree);
}
});
apiFunctions.setHandleRequest('getFocus', function(callback) {
callback(automationUtil.getFocus());
});
function removeTreeChangeObserver(observer) {
for (var id in automationUtil.treeChangeObserverMap) {
if (automationUtil.treeChangeObserverMap[id] == observer) {
RemoveTreeChangeObserver(id);
delete automationUtil.treeChangeObserverMap[id];
return;
}
}
}
apiFunctions.setHandleRequest('removeTreeChangeObserver', function(observer) {
removeTreeChangeObserver(observer);
});
function addTreeChangeObserver(filter, observer) {
removeTreeChangeObserver(observer);
var id = automationUtil.nextTreeChangeObserverId++;
AddTreeChangeObserver(id, filter);
automationUtil.treeChangeObserverMap[id] = observer;
}
apiFunctions.setHandleRequest('addTreeChangeObserver',
function(filter, observer) {
addTreeChangeObserver(filter, observer);
});
apiFunctions.setHandleRequest('setDocumentSelection', function(params) {
var anchorNodeImpl = privates(params.anchorObject).impl;
var focusNodeImpl = privates(params.focusObject).impl;
if (anchorNodeImpl.treeID !== focusNodeImpl.treeID)
throw new Error('Selection anchor and focus must be in the same tree.');
if (anchorNodeImpl.treeID === DESKTOP_TREE_ID) {
throw new Error('Use AutomationNode.setSelection to set the selection ' +
'in the desktop tree.');
}
automationInternal.performAction({ treeID: anchorNodeImpl.treeID,
automationNodeID: anchorNodeImpl.id,
actionType: 'setSelection'},
{ focusNodeID: focusNodeImpl.id,
anchorOffset: params.anchorOffset,
focusOffset: params.focusOffset });
});
});
automationInternal.onChildTreeID.addListener(function(treeID,
nodeID) {
var tree = AutomationRootNode.getOrCreate(treeID);
if (!tree)
return;
var node = privates(tree).impl.get(nodeID);
if (!node)
return;
// A WebView in the desktop tree has a different AX tree as its child.
// When we encounter a WebView with a child AX tree id that we don't
// currently have cached, explicitly request that AX tree from the
// browser process and set up a callback when it loads to attach that
// tree as a child of this node and fire appropriate events.
var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId');
if (!childTreeID)
return;
var subroot = AutomationRootNode.get(childTreeID);
if (!subroot || subroot.role == schema.EventType.unknown) {
automationUtil.storeTreeCallback(childTreeID, function(root) {
// Return early if the root has already been attached.
if (root.parent)
return;
privates(root).impl.setHostNode(node);
if (root.docLoaded) {
privates(root).impl.dispatchEvent(
schema.EventType.loadComplete, 'page');
}
privates(node).impl.dispatchEvent(
schema.EventType.childrenChanged, 'none');
});
automationInternal.enableFrame(childTreeID);
} else {
privates(subroot).impl.setHostNode(node);
}
});
automationInternal.onTreeChange.addListener(function(observerID,
treeID,
nodeID,
changeType) {
var tree = AutomationRootNode.getOrCreate(treeID);
if (!tree)
return;
var node = privates(tree).impl.get(nodeID);
if (!node)
return;
var observer = automationUtil.treeChangeObserverMap[observerID];
if (!observer)
return;
try {
observer({target: node, type: changeType});
} catch (e) {
exceptionHandler.handle('Error in tree change observer for ' +
treeChange.type, e);
}
});
automationInternal.onNodesRemoved.addListener(function(treeID, nodeIDs) {
var tree = AutomationRootNode.getOrCreate(treeID);
if (!tree)
return;
for (var i = 0; i < nodeIDs.length; i++) {
privates(tree).impl.remove(nodeIDs[i]);
}
});
/**
* Dispatch accessibility events fired on individual nodes to its
* corresponding AutomationNode. Handle focus events specially
* (see below).
*/
automationInternal.onAccessibilityEvent.addListener(function(eventParams) {
var id = eventParams.treeID;
var targetTree = AutomationRootNode.getOrCreate(id);
// Work around an issue where Chrome sends us 'blur' events on the
// root node when nothing has focus, we need to treat those as focus
// events but otherwise not handle blur events specially.
var isFocusEvent = false;
if (eventParams.eventType == schema.EventType.focus) {
isFocusEvent = true;
} else if (eventParams.eventType == schema.EventType.blur) {
var node = privates(targetTree).impl.get(eventParams.targetID);
if (node == node.root)
isFocusEvent = true;
}
// When we get a focus event, ignore the actual event target, and instead
// check what node has focus globally. If that represents a focus change,
// fire a focus event on the correct target.
if (isFocusEvent) {
var previousFocusedNode = automationUtil.focusedNode;
automationUtil.updateFocusedNode();
if (automationUtil.focusedNode &&
automationUtil.focusedNode == previousFocusedNode) {
return;
}
if (automationUtil.focusedNode) {
targetTree = automationUtil.focusedNode.root;
eventParams.treeID = privates(targetTree).impl.treeID;
eventParams.targetID = privates(automationUtil.focusedNode).impl.id;
}
}
if (!privates(targetTree).impl.onAccessibilityEvent(eventParams))
return;
// If we're not waiting on a callback to getTree(), we can early out here.
if (!(id in idToCallback))
return;
// We usually get a 'placeholder' tree first, which doesn't have any url
// attribute or child nodes. If we've got that, wait for the full tree before
// calling the callback.
// TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
if (id != DESKTOP_TREE_ID && !targetTree.url &&
targetTree.children.length == 0)
return;
// If the tree wasn't available when getTree() was called, the callback will
// have been cached in idToCallback, so call and delete it now that we
// have the complete tree.
for (var i = 0; i < idToCallback[id].length; i++) {
var callback = idToCallback[id][i];
callback(targetTree);
}
delete idToCallback[id];
});
automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) {
// Destroy the AutomationRootNode.
var targetTree = AutomationRootNode.get(id);
if (targetTree) {
privates(targetTree).impl.destroy();
AutomationRootNode.destroy(id);
} else {
logging.WARNING('no targetTree to destroy');
}
// Destroy the native cache of the accessibility tree.
DestroyAccessibilityTree(id);
});
automationInternal.onAccessibilityTreeSerializationError.addListener(
function(id) {
automationInternal.enableFrame(id);
});
var binding = automation.generate();
// Add additional accessibility bindings not specified in the automation IDL.
// Accessibility and automation share some APIs (see
// ui/accessibility/ax_enums.idl).
forEach(schema, function(k, v) {
binding[k] = v;
});
exports.$set('binding', binding);
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var utils = require('utils');
function AutomationEventImpl(type, target, eventFrom) {
this.propagationStopped = false;
this.type = type;
this.target = target;
this.eventPhase = Event.NONE;
this.eventFrom = eventFrom;
}
AutomationEventImpl.prototype = {
__proto__: null,
stopPropagation: function() {
this.propagationStopped = true;
},
};
function AutomationEvent() {
privates(AutomationEvent).constructPrivate(this, arguments);
}
utils.expose(AutomationEvent, AutomationEventImpl, {
functions: [
'stopPropagation',
],
readonly: [
'type',
'target',
'eventPhase',
'eventFrom',
],
});
exports.$set('AutomationEvent', AutomationEvent);
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var AutomationEvent = require('automationEvent').AutomationEvent;
var automationInternal =
require('binding').Binding.create('automationInternal').generate();
var exceptionHandler = require('uncaught_exception_handler');
var IsInteractPermitted =
requireNative('automationInternal').IsInteractPermitted;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?number} The id of the root node.
*/
var GetRootID = requireNative('automationInternal').GetRootID;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?string} The title of the document.
*/
var GetDocTitle = requireNative('automationInternal').GetDocTitle;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?string} The url of the document.
*/
var GetDocURL = requireNative('automationInternal').GetDocURL;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?boolean} True if the document has finished loading.
*/
var GetDocLoaded = requireNative('automationInternal').GetDocLoaded;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?number} The loading progress, from 0.0 to 1.0 (fully loaded).
*/
var GetDocLoadingProgress =
requireNative('automationInternal').GetDocLoadingProgress;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?number} The ID of the selection anchor object.
*/
var GetAnchorObjectID = requireNative('automationInternal').GetAnchorObjectID;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?number} The selection anchor offset.
*/
var GetAnchorOffset = requireNative('automationInternal').GetAnchorOffset;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?string} The selection anchor affinity.
*/
var GetAnchorAffinity = requireNative('automationInternal').GetAnchorAffinity;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?number} The ID of the selection focus object.
*/
var GetFocusObjectID = requireNative('automationInternal').GetFocusObjectID;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?number} The selection focus offset.
*/
var GetFocusOffset = requireNative('automationInternal').GetFocusOffset;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @return {?string} The selection focus affinity.
*/
var GetFocusAffinity = requireNative('automationInternal').GetFocusAffinity;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?number} The id of the node's parent, or undefined if it's the
* root of its tree or if the tree or node wasn't found.
*/
var GetParentID = requireNative('automationInternal').GetParentID;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?number} The number of children of the node, or undefined if
* the tree or node wasn't found.
*/
var GetChildCount = requireNative('automationInternal').GetChildCount;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {number} childIndex An index of a child of this node.
* @return {?number} The id of the child at the given index, or undefined
* if the tree or node or child at that index wasn't found.
*/
var GetChildIDAtIndex = requireNative('automationInternal').GetChildIDAtIndex;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?number} The ids of the children of the node, or undefined
* if the tree or node wasn't found.
*/
var GetChildIds = requireNative('automationInternal').GetChildIDs;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Object} An object mapping html attributes to values.
*/
var GetHtmlAttributes = requireNative('automationInternal').GetHtmlAttributes;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?number} The index of this node in its parent, or undefined if
* the tree or node or node parent wasn't found.
*/
var GetIndexInParent = requireNative('automationInternal').GetIndexInParent;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Object} An object with a string key for every state flag set,
* or undefined if the tree or node or node parent wasn't found.
*/
var GetState = requireNative('automationInternal').GetState;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {string} The role of the node, or undefined if the tree or
* node wasn't found.
*/
var GetRole = requireNative('automationInternal').GetRole;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?automation.Rect} The location of the node, or undefined if
* the tree or node wasn't found.
*/
var GetLocation = requireNative('automationInternal').GetLocation;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {number} startIndex The start index of the range.
* @param {number} endIndex The end index of the range.
* @return {?automation.Rect} The bounding box of the subrange of this node,
* or the location if there are no subranges, or undefined if
* the tree or node wasn't found.
*/
var GetBoundsForRange = requireNative('automationInternal').GetBoundsForRange;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {!Array.<number>} The text offset where each line starts, or an empty
* array if this node has no text content, or undefined if the tree or node
* was not found.
*/
var GetLineStartOffsets = requireNative(
'automationInternal').GetLineStartOffsets;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} attr The name of a string attribute.
* @return {?string} The value of this attribute, or undefined if the tree,
* node, or attribute wasn't found.
*/
var GetStringAttribute = requireNative('automationInternal').GetStringAttribute;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} attr The name of an attribute.
* @return {?boolean} The value of this attribute, or undefined if the tree,
* node, or attribute wasn't found.
*/
var GetBoolAttribute = requireNative('automationInternal').GetBoolAttribute;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} attr The name of an attribute.
* @return {?number} The value of this attribute, or undefined if the tree,
* node, or attribute wasn't found.
*/
var GetIntAttribute = requireNative('automationInternal').GetIntAttribute;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} attr The name of an attribute.
* @return {?number} The value of this attribute, or undefined if the tree,
* node, or attribute wasn't found.
*/
var GetFloatAttribute = requireNative('automationInternal').GetFloatAttribute;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} attr The name of an attribute.
* @return {?Array.<number>} The value of this attribute, or undefined
* if the tree, node, or attribute wasn't found.
*/
var GetIntListAttribute =
requireNative('automationInternal').GetIntListAttribute;
/**
* @param {number} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} attr The name of an HTML attribute.
* @return {?string} The value of this attribute, or undefined if the tree,
* node, or attribute wasn't found.
*/
var GetHtmlAttribute = requireNative('automationInternal').GetHtmlAttribute;
var lastError = require('lastError');
var logging = requireNative('logging');
var schema = requireNative('automationInternal').GetSchemaAdditions();
var utils = require('utils');
/**
* A single node in the Automation tree.
* @param {AutomationRootNodeImpl} root The root of the tree.
* @constructor
*/
function AutomationNodeImpl(root) {
this.rootImpl = root;
this.hostNode_ = null;
this.listeners = {__proto__: null};
}
AutomationNodeImpl.prototype = {
__proto__: null,
treeID: -1,
id: -1,
isRootNode: false,
detach: function() {
this.rootImpl = null;
this.hostNode_ = null;
this.listeners = {__proto__: null};
},
get root() {
return this.rootImpl && this.rootImpl.wrapper;
},
get parent() {
if (!this.rootImpl)
return undefined;
if (this.hostNode_)
return this.hostNode_;
var parentID = GetParentID(this.treeID, this.id);
return this.rootImpl.get(parentID);
},
get htmlAttributes() {
return GetHtmlAttributes(this.treeID, this.id) || {};
},
get state() {
return GetState(this.treeID, this.id) || {};
},
get role() {
return GetRole(this.treeID, this.id);
},
get location() {
return GetLocation(this.treeID, this.id);
},
boundsForRange: function(startIndex, endIndex) {
return GetBoundsForRange(this.treeID, this.id, startIndex, endIndex);
},
get indexInParent() {
return GetIndexInParent(this.treeID, this.id);
},
get lineStartOffsets() {
return GetLineStartOffsets(this.treeID, this.id);
},
get childTree() {
var childTreeID = GetIntAttribute(this.treeID, this.id, 'childTreeId');
if (childTreeID)
return AutomationRootNodeImpl.get(childTreeID);
},
get firstChild() {
if (!this.rootImpl)
return undefined;
if (this.childTree)
return this.childTree;
if (!GetChildCount(this.treeID, this.id))
return undefined;
var firstChildID = GetChildIDAtIndex(this.treeID, this.id, 0);
return this.rootImpl.get(firstChildID);
},
get lastChild() {
if (!this.rootImpl)
return undefined;
if (this.childTree)
return this.childTree;
var count = GetChildCount(this.treeID, this.id);
if (!count)
return undefined;
var lastChildID = GetChildIDAtIndex(this.treeID, this.id, count - 1);
return this.rootImpl.get(lastChildID);
},
get children() {
if (!this.rootImpl)
return [];
if (this.childTree)
return [this.childTree];
var children = [];
var childIds = GetChildIds(this.treeID, this.id);
for (var i = 0; i < childIds.length; ++i) {
var childID = childIds[i];
var child = this.rootImpl.get(childID);
$Array.push(children, child);
}
return children;
},
get previousSibling() {
var parent = this.parent;
if (!parent)
return undefined;
parent = privates(parent).impl;
var indexInParent = GetIndexInParent(this.treeID, this.id);
return this.rootImpl.get(
GetChildIDAtIndex(parent.treeID, parent.id, indexInParent - 1));
},
get nextSibling() {
var parent = this.parent;
if (!parent)
return undefined;
parent = privates(parent).impl;
var indexInParent = GetIndexInParent(this.treeID, this.id);
return this.rootImpl.get(
GetChildIDAtIndex(parent.treeID, parent.id, indexInParent + 1));
},
doDefault: function() {
this.performAction_('doDefault');
},
focus: function() {
this.performAction_('focus');
},
makeVisible: function() {
this.performAction_('makeVisible');
},
setSelection: function(startIndex, endIndex) {
if (this.role == 'textField' || this.role == 'textBox') {
this.performAction_('setSelection',
{ focusNodeID: this.id,
anchorOffset: startIndex,
focusOffset: endIndex });
}
},
setSequentialFocusNavigationStartingPoint: function() {
this.performAction_('setSequentialFocusNavigationStartingPoint');
},
showContextMenu: function() {
this.performAction_('showContextMenu');
},
domQuerySelector: function(selector, callback) {
if (!this.rootImpl)
callback();
automationInternal.querySelector(
{ treeID: this.rootImpl.treeID,
automationNodeID: this.id,
selector: selector },
$Function.bind(this.domQuerySelectorCallback_, this, callback));
},
find: function(params) {
return this.findInternal_(params);
},
findAll: function(params) {
return this.findInternal_(params, []);
},
matches: function(params) {
return this.matchInternal_(params);
},
addEventListener: function(eventType, callback, capture) {
this.removeEventListener(eventType, callback);
if (!this.listeners[eventType])
this.listeners[eventType] = [];
$Array.push(this.listeners[eventType], {
__proto__: null,
callback: callback,
capture: !!capture,
});
},
// TODO(dtseng/aboxhall): Check this impl against spec.
removeEventListener: function(eventType, callback) {
if (this.listeners[eventType]) {
var listeners = this.listeners[eventType];
for (var i = 0; i < listeners.length; i++) {
if (callback === listeners[i].callback)
$Array.splice(listeners, i, 1);
}
}
},
toJSON: function() {
return { treeID: this.treeID,
id: this.id,
role: this.role,
attributes: this.attributes };
},
dispatchEvent: function(eventType, eventFrom, mouseX, mouseY) {
var path = [];
var parent = this.parent;
while (parent) {
$Array.push(path, parent);
parent = parent.parent;
}
var event = new AutomationEvent(eventType, this.wrapper, eventFrom);
event.mouseX = mouseX;
event.mouseY = mouseY;
// Dispatch the event through the propagation path in three phases:
// - capturing: starting from the root and going down to the target's parent
// - targeting: dispatching the event on the target itself
// - bubbling: starting from the target's parent, going back up to the root.
// At any stage, a listener may call stopPropagation() on the event, which
// will immediately stop event propagation through this path.
if (this.dispatchEventAtCapturing_(event, path)) {
if (this.dispatchEventAtTargeting_(event, path))
this.dispatchEventAtBubbling_(event, path);
}
},
toString: function() {
var parentID = GetParentID(this.treeID, this.id);
var childTreeID = GetIntAttribute(this.treeID, this.id, 'childTreeId');
var count = GetChildCount(this.treeID, this.id);
var childIDs = [];
for (var i = 0; i < count; ++i) {
var childID = GetChildIDAtIndex(this.treeID, this.id, i);
$Array.push(childIDs, childID);
}
var result = 'node id=' + this.id +
' role=' + this.role +
' state=' + $JSON.stringify(this.state) +
' parentID=' + parentID +
' childIds=' + $JSON.stringify(childIDs);
if (this.hostNode_) {
var hostNodeImpl = privates(this.hostNode_).impl;
result += ' host treeID=' + hostNodeImpl.treeID +
' host nodeID=' + hostNodeImpl.id;
}
if (childTreeID)
result += ' childTreeID=' + childTreeID;
return result;
},
dispatchEventAtCapturing_: function(event, path) {
privates(event).impl.eventPhase = Event.CAPTURING_PHASE;
for (var i = path.length - 1; i >= 0; i--) {
this.fireEventListeners_(path[i], event);
if (privates(event).impl.propagationStopped)
return false;
}
return true;
},
dispatchEventAtTargeting_: function(event) {
privates(event).impl.eventPhase = Event.AT_TARGET;
this.fireEventListeners_(this.wrapper, event);
return !privates(event).impl.propagationStopped;
},
dispatchEventAtBubbling_: function(event, path) {
privates(event).impl.eventPhase = Event.BUBBLING_PHASE;
for (var i = 0; i < path.length; i++) {
this.fireEventListeners_(path[i], event);
if (privates(event).impl.propagationStopped)
return false;
}
return true;
},
fireEventListeners_: function(node, event) {
var nodeImpl = privates(node).impl;
if (!nodeImpl.rootImpl)
return;
var listeners = nodeImpl.listeners[event.type];
if (!listeners)
return;
var eventPhase = event.eventPhase;
for (var i = 0; i < listeners.length; i++) {
if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture)
continue;
if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture)
continue;
try {
listeners[i].callback(event);
} catch (e) {
exceptionHandler.handle('Error in event handler for ' + event.type +
' during phase ' + eventPhase, e);
}
}
},
performAction_: function(actionType, opt_args) {
if (!this.rootImpl)
return;
// Not yet initialized.
if (this.rootImpl.treeID === undefined ||
this.id === undefined) {
return;
}
// Check permissions.
if (!IsInteractPermitted()) {
throw new Error(actionType + ' requires {"desktop": true} or' +
' {"interact": true} in the "automation" manifest key.');
}
automationInternal.performAction({ treeID: this.rootImpl.treeID,
automationNodeID: this.id,
actionType: actionType },
opt_args || {});
},
domQuerySelectorCallback_: function(userCallback, resultAutomationNodeID) {
// resultAutomationNodeID could be zero or undefined or (unlikely) null;
// they all amount to the same thing here, which is that no node was
// returned.
if (!resultAutomationNodeID || !this.rootImpl) {
userCallback(null);
return;
}
var resultNode = this.rootImpl.get(resultAutomationNodeID);
if (!resultNode) {
logging.WARNING('Query selector result not in tree: ' +
resultAutomationNodeID);
userCallback(null);
}
userCallback(resultNode);
},
findInternal_: function(params, opt_results) {
var result = null;
this.forAllDescendants_(function(node) {
if (privates(node).impl.matchInternal_(params)) {
if (opt_results)
$Array.push(opt_results, node);
else
result = node;
return !opt_results;
}
});
if (opt_results)
return opt_results;
return result;
},
/**
* Executes a closure for all of this node's descendants, in pre-order.
* Early-outs if the closure returns true.
* @param {Function(AutomationNode):boolean} closure Closure to be executed
* for each node. Return true to early-out the traversal.
*/
forAllDescendants_: function(closure) {
var stack = $Array.reverse(this.wrapper.children);
while (stack.length > 0) {
var node = $Array.pop(stack);
if (closure(node))
return;
var children = node.children;
for (var i = children.length - 1; i >= 0; i--)
$Array.push(stack, children[i]);
}
},
matchInternal_: function(params) {
if ($Object.keys(params).length === 0)
return false;
if ('role' in params && this.role != params.role)
return false;
if ('state' in params) {
for (var state in params.state) {
if (params.state[state] != (state in this.state))
return false;
}
}
if ('attributes' in params) {
for (var attribute in params.attributes) {
var attrValue = params.attributes[attribute];
if (typeof attrValue != 'object') {
if (this[attribute] !== attrValue)
return false;
} else if (attrValue instanceof $RegExp.self) {
if (typeof this[attribute] != 'string')
return false;
if (!attrValue.test(this[attribute]))
return false;
} else {
// TODO(aboxhall): handle intlist case.
return false;
}
}
}
return true;
}
};
var stringAttributes = [
'accessKey',
'action',
'ariaInvalidValue',
'autoComplete',
'containerLiveRelevant',
'containerLiveStatus',
'description',
'display',
'dropeffect',
'help',
'htmlTag',
'language',
'liveRelevant',
'liveStatus',
'name',
'placeholder',
'shortcut',
'textInputType',
'url',
'value'];
var boolAttributes = [
'ariaReadonly',
'buttonMixed',
'canSetValue',
'canvasHasFallback',
'containerLiveAtomic',
'containerLiveBusy',
'grabbed',
'isAxTreeHost',
'liveAtomic',
'liveBusy',
'updateLocationOnly'];
var intAttributes = [
'backgroundColor',
'color',
'colorValue',
'descriptionFrom',
'hierarchicalLevel',
'invalidState',
'nameFrom',
'posInSet',
'scrollX',
'scrollXMax',
'scrollXMin',
'scrollY',
'scrollYMax',
'scrollYMin',
'setSize',
'sortDirection',
'tableCellColumnIndex',
'tableCellColumnSpan',
'tableCellRowIndex',
'tableCellRowSpan',
'tableColumnCount',
'tableColumnIndex',
'tableRowCount',
'tableRowIndex',
'textDirection',
'textSelEnd',
'textSelStart',
'textStyle'];
var nodeRefAttributes = [
['activedescendantId', 'activeDescendant'],
['nextOnLineId', 'nextOnLine'],
['previousOnLineId', 'previousOnLine'],
['tableColumnHeaderId', 'tableColumnHeader'],
['tableHeaderId', 'tableHeader'],
['tableRowHeaderId', 'tableRowHeader'],
['titleUiElement', 'titleUIElement']];
var intListAttributes = [
'characterOffsets',
'lineBreaks',
'markerEnds',
'markerStarts',
'markerTypes',
'wordEnds',
'wordStarts'];
var nodeRefListAttributes = [
['cellIds', 'cells'],
['controlsIds', 'controls'],
['describedbyIds', 'describedBy'],
['flowtoIds', 'flowTo'],
['labelledbyIds', 'labelledBy'],
['uniqueCellIds', 'uniqueCells']];
var floatAttributes = [
'valueForRange',
'minValueForRange',
'maxValueForRange',
'fontSize'];
var htmlAttributes = [
['type', 'inputType']];
var publicAttributes = [];
$Array.forEach(stringAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetStringAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(boolAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetBoolAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(intAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetIntAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(nodeRefAttributes, function(params) {
var srcAttributeName = params[0];
var dstAttributeName = params[1];
$Array.push(publicAttributes, dstAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
__proto__: null,
get: function() {
var id = GetIntAttribute(this.treeID, this.id, srcAttributeName);
if (id && this.rootImpl)
return this.rootImpl.get(id);
else
return undefined;
}
});
});
$Array.forEach(intListAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetIntListAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(nodeRefListAttributes, function(params) {
var srcAttributeName = params[0];
var dstAttributeName = params[1];
$Array.push(publicAttributes, dstAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
__proto__: null,
get: function() {
var ids = GetIntListAttribute(this.treeID, this.id, srcAttributeName);
if (!ids || !this.rootImpl)
return undefined;
var result = [];
for (var i = 0; i < ids.length; ++i) {
var node = this.rootImpl.get(ids[i]);
if (node)
$Array.push(result, node);
}
return result;
}
});
});
$Array.forEach(floatAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetFloatAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(htmlAttributes, function(params) {
var srcAttributeName = params[0];
var dstAttributeName = params[1];
$Array.push(publicAttributes, dstAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
__proto__: null,
get: function() {
return GetHtmlAttribute(this.treeID, this.id, srcAttributeName);
}
});
});
/**
* AutomationRootNode.
*
* An AutomationRootNode is the javascript end of an AXTree living in the
* browser. AutomationRootNode handles unserializing incremental updates from
* the source AXTree. Each update contains node data that form a complete tree
* after applying the update.
*
* A brief note about ids used through this class. The source AXTree assigns
* unique ids per node and we use these ids to build a hash to the actual
* AutomationNode object.
* Thus, tree traversals amount to a lookup in our hash.
*
* The tree itself is identified by the accessibility tree id of the
* renderer widget host.
* @constructor
*/
function AutomationRootNodeImpl(treeID) {
$Function.call(AutomationNodeImpl, this, this);
this.treeID = treeID;
this.axNodeDataCache_ = {__proto__: null};
}
utils.defineProperty(AutomationRootNodeImpl, 'idToAutomationRootNode_',
{__proto__: null});
utils.defineProperty(AutomationRootNodeImpl, 'get', function(treeID) {
var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
return result || undefined;
});
utils.defineProperty(AutomationRootNodeImpl, 'getOrCreate', function(treeID) {
if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID])
return AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
var result = new AutomationRootNode(treeID);
AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result;
return result;
});
utils.defineProperty(AutomationRootNodeImpl, 'destroy', function(treeID) {
delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
});
AutomationRootNodeImpl.prototype = {
__proto__: AutomationNodeImpl.prototype,
/**
* @type {boolean}
*/
isRootNode: true,
/**
* @type {number}
*/
treeID: -1,
/**
* The parent of this node from a different tree.
* @type {?AutomationNode}
* @private
*/
hostNode_: null,
/**
* A map from id to AutomationNode.
* @type {Object.<number, AutomationNode>}
* @private
*/
axNodeDataCache_: null,
get id() {
var result = GetRootID(this.treeID);
// Don't return undefined, because the id is often passed directly
// as an argument to a native binding that expects only a valid number.
if (result === undefined)
return -1;
return result;
},
get docUrl() {
return GetDocURL(this.treeID);
},
get docTitle() {
return GetDocTitle(this.treeID);
},
get docLoaded() {
return GetDocLoaded(this.treeID);
},
get docLoadingProgress() {
return GetDocLoadingProgress(this.treeID);
},
get anchorObject() {
var id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
else
return undefined;
},
get anchorOffset() {
var id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return GetAnchorOffset(this.treeID);
},
get anchorAffinity() {
var id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return GetAnchorAffinity(this.treeID);
},
get focusObject() {
var id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
else
return undefined;
},
get focusOffset() {
var id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return GetFocusOffset(this.treeID);
},
get focusAffinity() {
var id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return GetFocusAffinity(this.treeID);
},
get: function(id) {
if (id == undefined)
return undefined;
if (id == this.id)
return this.wrapper;
var obj = this.axNodeDataCache_[id];
if (obj)
return obj;
obj = new AutomationNode(this);
privates(obj).impl.treeID = this.treeID;
privates(obj).impl.id = id;
this.axNodeDataCache_[id] = obj;
return obj;
},
remove: function(id) {
if (this.axNodeDataCache_[id])
privates(this.axNodeDataCache_[id]).impl.detach();
delete this.axNodeDataCache_[id];
},
destroy: function() {
this.dispatchEvent(schema.EventType.destroyed, 'none');
for (var id in this.axNodeDataCache_)
this.remove(id);
this.detach();
},
setHostNode(hostNode) {
this.hostNode_ = hostNode;
},
onAccessibilityEvent: function(eventParams) {
var targetNode = this.get(eventParams.targetID);
if (targetNode) {
var targetNodeImpl = privates(targetNode).impl;
targetNodeImpl.dispatchEvent(
eventParams.eventType, eventParams.eventFrom,
eventParams.mouseX, eventParams.mouseY);
} else {
logging.WARNING('Got ' + eventParams.eventType +
' event on unknown node: ' + eventParams.targetID +
'; this: ' + this.id);
}
return true;
},
toString: function() {
function toStringInternal(nodeImpl, indent) {
if (!nodeImpl)
return '';
var output = '';
if (nodeImpl.isRootNode)
output += indent + 'tree id=' + nodeImpl.treeID + '\n';
output += indent +
$Function.call(AutomationNodeImpl.prototype.toString, nodeImpl) + '\n';
indent += ' ';
var children = nodeImpl.children;
for (var i = 0; i < children.length; ++i)
output += toStringInternal(privates(children[i]).impl, indent);
return output;
}
return toStringInternal(this, '');
},
};
function AutomationNode() {
privates(AutomationNode).constructPrivate(this, arguments);
}
utils.expose(AutomationNode, AutomationNodeImpl, {
functions: [
'doDefault',
'find',
'findAll',
'focus',
'makeVisible',
'matches',
'setSelection',
'setSequentialFocusNavigationStartingPoint',
'showContextMenu',
'addEventListener',
'removeEventListener',
'domQuerySelector',
'toString',
'boundsForRange',
],
readonly: $Array.concat(publicAttributes, [
'parent',
'firstChild',
'lastChild',
'children',
'previousSibling',
'nextSibling',
'isRootNode',
'role',
'state',
'location',
'indexInParent',
'lineStartOffsets',
'root',
'htmlAttributes',
]),
});
function AutomationRootNode() {
privates(AutomationRootNode).constructPrivate(this, arguments);
}
utils.expose(AutomationRootNode, AutomationRootNodeImpl, {
superclass: AutomationNode,
readonly: [
'docTitle',
'docUrl',
'docLoaded',
'docLoadingProgress',
'anchorObject',
'anchorOffset',
'anchorAffinity',
'focusObject',
'focusOffset',
'focusAffinity',
],
});
utils.defineProperty(AutomationRootNode, 'get', function(treeID) {
return AutomationRootNodeImpl.get(treeID);
});
utils.defineProperty(AutomationRootNode, 'getOrCreate', function(treeID) {
return AutomationRootNodeImpl.getOrCreate(treeID);
});
utils.defineProperty(AutomationRootNode, 'destroy', function(treeID) {
AutomationRootNodeImpl.destroy(treeID);
});
exports.$set('AutomationNode', AutomationNode);
exports.$set('AutomationRootNode', AutomationRootNode);
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the browserAction API.
var binding = require('binding').Binding.create('browserAction');
var setIcon = require('setIcon').setIcon;
var getExtensionViews = requireNative('runtime').GetExtensionViews;
var sendRequest = require('sendRequest').sendRequest;
var lastError = require('lastError');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('setIcon', function(details, callback) {
setIcon(details, function(args) {
sendRequest(this.name, [args, callback], this.definition.parameters);
}.bind(this));
});
apiFunctions.setCustomCallback('openPopup',
function(name, request, callback, response) {
if (!callback)
return;
if (lastError.hasError(chrome)) {
callback();
} else {
var views = getExtensionViews(-1, -1, 'POPUP');
callback(views.length > 0 ? views[0] : null);
}
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Cast Streaming RtpStream API.
var binding = require('binding').Binding.create('cast.streaming.rtpStream');
var natives = requireNative('cast_streaming_natives');
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('destroy',
function(transportId) {
natives.DestroyCastRtpStream(transportId);
});
apiFunctions.setHandleRequest('getSupportedParams',
function(transportId) {
return natives.GetSupportedParamsCastRtpStream(transportId);
});
apiFunctions.setHandleRequest('start',
function(transportId, params) {
natives.StartCastRtpStream(transportId, params);
});
apiFunctions.setHandleRequest('stop',
function(transportId) {
natives.StopCastRtpStream(transportId);
});
apiFunctions.setHandleRequest('toggleLogging',
function(transportId, enable) {
natives.ToggleLogging(transportId, enable);
});
apiFunctions.setHandleRequest('getRawEvents',
function(transportId, extraData, callback) {
natives.GetRawEvents(transportId, extraData, callback);
});
apiFunctions.setHandleRequest('getStats',
function(transportId, callback) {
natives.GetStats(transportId, callback);
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Cast Streaming Session API.
var binding = require('binding').Binding.create('cast.streaming.session');
var natives = requireNative('cast_streaming_natives');
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('create',
function(audioTrack, videoTrack, callback) {
natives.CreateSession(audioTrack, videoTrack, callback);
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Cast Streaming UdpTransport API.
var binding = require('binding').Binding.create('cast.streaming.udpTransport');
var natives = requireNative('cast_streaming_natives');
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('destroy', function(transportId) {
natives.DestroyCastUdpTransport(transportId);
});
apiFunctions.setHandleRequest('setDestination',
function(transportId, destination) {
natives.SetDestinationCastUdpTransport(transportId, destination);
});
apiFunctions.setHandleRequest('setOptions',
function(transportId, options) {
natives.SetOptionsCastUdpTransport(transportId, options);
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Cast Streaming Session API.
var binding = require('binding').Binding.create(
'cast.streaming.receiverSession');
var natives = requireNative('cast_streaming_natives');
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('createAndBind',
function(ap, vp, local, weidgth, height, fr, url, cb, op) {
natives.StartCastRtpReceiver(
ap, vp, local, weidgth, height, fr, url, cb, op);
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var Event = require('event_bindings').Event;
var sendRequest = require('sendRequest').sendRequest;
var validate = require('schemaUtils').validate;
function extendSchema(schema) {
var extendedSchema = $Array.slice(schema);
extendedSchema.unshift({'type': 'string'});
return extendedSchema;
}
function ChromeDirectSetting(prefKey, valueSchema, schema) {
var getFunctionParameters = function(name) {
var f = $Array.filter(
schema.functions, function(f) { return f.name === name; })[0];
return f.parameters;
};
this.get = function(details, callback) {
var getSchema = getFunctionParameters('get');
validate([details, callback], getSchema);
return sendRequest('types.private.ChromeDirectSetting.get',
[prefKey, details, callback],
extendSchema(getSchema));
};
this.set = function(details, callback) {
var setSchema = $Array.slice(getFunctionParameters('set'));
setSchema[0].properties.value = valueSchema;
validate([details, callback], setSchema);
return sendRequest('types.private.ChromeDirectSetting.set',
[prefKey, details, callback],
extendSchema(setSchema));
};
this.clear = function(details, callback) {
var clearSchema = getFunctionParameters('clear');
validate([details, callback], clearSchema);
return sendRequest('types.private.ChromeDirectSetting.clear',
[prefKey, details, callback],
extendSchema(clearSchema));
};
this.onChange = new Event('types.private.ChromeDirectSetting.' +
prefKey +
'.onChange');
};
exports.$set('ChromeDirectSetting', ChromeDirectSetting);
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var Event = require('event_bindings').Event;
var sendRequest = require('sendRequest').sendRequest;
var validate = require('schemaUtils').validate;
function extendSchema(schema) {
var extendedSchema = $Array.slice(schema);
$Array.unshift(extendedSchema, {'type': 'string'});
return extendedSchema;
}
// TODO(devlin): Maybe find a way to combine this and ContentSetting.
function ChromeSetting(prefKey, valueSchema, schema) {
var getFunctionParameters = function(name) {
var f = $Array.filter(
schema.functions, function(f) { return f.name === name; })[0];
return f.parameters;
};
this.get = function(details, callback) {
var getSchema = getFunctionParameters('get');
validate([details, callback], getSchema);
return sendRequest('types.ChromeSetting.get',
[prefKey, details, callback],
extendSchema(getSchema));
};
this.set = function(details, callback) {
// The set schema included in the Schema object is generic, since it varies
// per-setting. However, this is only ever for a single setting, so we can
// enforce the types more thoroughly.
var rawSetSchema = getFunctionParameters('set');
var rawSettingParam = rawSetSchema[0];
var props = $Object.assign({}, rawSettingParam.properties);
props.value = valueSchema;
var modSettingParam = {
name: rawSettingParam.name,
type: rawSettingParam.type,
properties: props,
};
var modSetSchema = $Array.slice(rawSetSchema);
modSetSchema[0] = modSettingParam;
validate([details, callback], modSetSchema);
return sendRequest('types.ChromeSetting.set',
[prefKey, details, callback],
extendSchema(modSetSchema));
};
this.clear = function(details, callback) {
var clearSchema = getFunctionParameters('clear');
validate([details, callback], clearSchema);
return sendRequest('types.ChromeSetting.clear',
[prefKey, details, callback],
extendSchema(clearSchema));
};
this.onChange = new Event('types.ChromeSetting.' + prefKey + '.onChange');
};
exports.$set('ChromeSetting', ChromeSetting);
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var binding = require('binding').Binding.create('chromeWebViewInternal');
var contextMenusHandlers = require('contextMenusHandlers');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
var handlers = contextMenusHandlers.create(true /* isWebview */);
apiFunctions.setHandleRequest('contextMenusCreate',
handlers.requestHandlers.create);
apiFunctions.setCustomCallback('contextMenusCreate',
handlers.callbacks.create);
apiFunctions.setCustomCallback('contextMenusUpdate',
handlers.callbacks.update);
apiFunctions.setCustomCallback('contextMenusRemove',
handlers.callbacks.remove);
apiFunctions.setCustomCallback('contextMenusRemoveAll',
handlers.callbacks.removeAll);
});
exports.$set('ChromeWebView', binding.generate());
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This module implements chrome-specific <webview> API.
// See web_view_api_methods.js for details.
var ChromeWebView = require('chromeWebViewInternal').ChromeWebView;
var ChromeWebViewSchema =
requireNative('schema_registry').GetSchema('chromeWebViewInternal');
var CreateEvent = require('guestViewEvents').CreateEvent;
var EventBindings = require('event_bindings');
var GuestViewInternalNatives = requireNative('guest_view_internal');
var idGeneratorNatives = requireNative('id_generator');
var utils = require('utils');
var WebViewImpl = require('webView').WebViewImpl;
// This is the only "webViewInternal.onClicked" named event for this renderer.
//
// Since we need an event per <webview>, we define events with suffix
// (subEventName) in each of the <webview>. Behind the scenes, this event is
// registered as a ContextMenusEvent, with filter set to the webview's
// |viewInstanceId|. Any time a ContextMenusEvent is dispatched, we re-dispatch
// it to the subEvent's listeners. This way
// <webview>.contextMenus.onClicked behave as a regular chrome Event type.
var ContextMenusEvent = CreateEvent('chromeWebViewInternal.onClicked');
// See comment above.
var ContextMenusHandlerEvent =
CreateEvent('chromeWebViewInternal.onContextMenuShow');
// -----------------------------------------------------------------------------
// ContextMenusOnClickedEvent object.
// This event is exposed as <webview>.contextMenus.onClicked.
function ContextMenusOnClickedEvent(webViewInstanceId,
opt_eventName,
opt_argSchemas,
opt_eventOptions) {
var subEventName = GetUniqueSubEventName(opt_eventName);
$Function.call(EventBindings.Event,
this,
subEventName,
opt_argSchemas,
opt_eventOptions,
webViewInstanceId);
var view = GuestViewInternalNatives.GetViewFromID(webViewInstanceId);
if (!view) {
return;
}
view.events.addScopedListener(ContextMenusEvent, $Function.bind(function() {
// Re-dispatch to subEvent's listeners.
$Function.apply(this.dispatch, this, $Array.slice(arguments));
}, this), {instanceId: webViewInstanceId});
}
$Object.setPrototypeOf(ContextMenusOnClickedEvent.prototype,
EventBindings.Event.prototype);
// This event is exposed as <webview>.contextMenus.onShow.
function ContextMenusOnContextMenuEvent(webViewInstanceId,
opt_eventName,
opt_argSchemas,
opt_eventOptions) {
var subEventName = GetUniqueSubEventName(opt_eventName);
$Function.call(EventBindings.Event,
this,
subEventName,
opt_argSchemas,
opt_eventOptions,
webViewInstanceId);
var view = GuestViewInternalNatives.GetViewFromID(webViewInstanceId);
if (!view) {
return;
}
view.events.addScopedListener(
ContextMenusHandlerEvent, $Function.bind(function(e) {
var defaultPrevented = false;
var event = {
'preventDefault': function() { defaultPrevented = true; }
};
// Re-dispatch to subEvent's listeners.
$Function.apply(this.dispatch, this, [event]);
if (!defaultPrevented) {
// TODO(lazyboy): Remove |items| parameter completely from
// ChromeWebView.showContextMenu as we don't do anything useful with it
// currently.
var items = [];
var guestInstanceId = GuestViewInternalNatives.
GetViewFromID(webViewInstanceId).guest.getId();
ChromeWebView.showContextMenu(guestInstanceId, e.requestId, items);
}
}, this), {instanceId: webViewInstanceId});
}
$Object.setPrototypeOf(ContextMenusOnContextMenuEvent.prototype,
EventBindings.Event.prototype);
// -----------------------------------------------------------------------------
// WebViewContextMenusImpl object.
// An instance of this class is exposed as <webview>.contextMenus.
function WebViewContextMenusImpl(viewInstanceId) {
this.viewInstanceId_ = viewInstanceId;
}
$Object.setPrototypeOf(WebViewContextMenusImpl.prototype, null);
WebViewContextMenusImpl.prototype.create = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(ChromeWebView.contextMenusCreate, null, args);
};
WebViewContextMenusImpl.prototype.remove = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(ChromeWebView.contextMenusRemove, null, args);
};
WebViewContextMenusImpl.prototype.removeAll = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(ChromeWebView.contextMenusRemoveAll, null, args);
};
WebViewContextMenusImpl.prototype.update = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(ChromeWebView.contextMenusUpdate, null, args);
};
function WebViewContextMenus() {
privates(WebViewContextMenus).constructPrivate(this, arguments);
}
utils.expose(WebViewContextMenus, WebViewContextMenusImpl, {
functions: [
'create',
'remove',
'removeAll',
'update',
],
});
// -----------------------------------------------------------------------------
WebViewImpl.prototype.maybeSetupContextMenus = function() {
if (!this.contextMenusOnContextMenuEvent_) {
var eventName = 'chromeWebViewInternal.onContextMenuShow';
var eventSchema =
utils.lookup(ChromeWebViewSchema.events, 'name', 'onShow');
var eventOptions = {supportsListeners: true};
this.contextMenusOnContextMenuEvent_ = new ContextMenusOnContextMenuEvent(
this.viewInstanceId, eventName, eventSchema, eventOptions);
}
var createContextMenus = $Function.bind(function() {
return this.weakWrapper(function() {
if (this.contextMenus_) {
return this.contextMenus_;
}
this.contextMenus_ = new WebViewContextMenus(this.viewInstanceId);
// Define 'onClicked' event property on |this.contextMenus_|.
var getOnClickedEvent = $Function.bind(function() {
return this.weakWrapper(function() {
if (!this.contextMenusOnClickedEvent_) {
var eventName = 'chromeWebViewInternal.onClicked';
var eventSchema =
utils.lookup(ChromeWebViewSchema.events, 'name', 'onClicked');
var eventOptions = {supportsListeners: true};
var onClickedEvent = new ContextMenusOnClickedEvent(
this.viewInstanceId, eventName, eventSchema, eventOptions);
this.contextMenusOnClickedEvent_ = onClickedEvent;
return onClickedEvent;
}
return this.contextMenusOnClickedEvent_;
});
}, this);
$Object.defineProperty(
this.contextMenus_,
'onClicked',
{get: getOnClickedEvent(), enumerable: true});
$Object.defineProperty(
this.contextMenus_,
'onShow',
{
get: this.weakWrapper(function() {
return this.contextMenusOnContextMenuEvent_;
}),
enumerable: true
});
return this.contextMenus_;
});
}, this);
// Expose <webview>.contextMenus object.
$Object.defineProperty(
this.element,
'contextMenus',
{
get: createContextMenus(),
enumerable: true
});
};
function GetUniqueSubEventName(eventName) {
return eventName + '/' + idGeneratorNatives.GetNextId();
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the contentSettings API.
var sendRequest = require('sendRequest').sendRequest;
var validate = require('schemaUtils').validate;
// Some content types have been removed and no longer correspond to a real
// content setting. Instead, these always return a fixed dummy value, and issue
// a warning when accessed. This maps the content type name to the dummy value.
var DEPRECATED_CONTENT_TYPES = {
__proto__: null,
fullscreen: 'allow',
mouselock: 'allow',
};
function extendSchema(schema) {
var extendedSchema = $Array.slice(schema);
$Array.unshift(extendedSchema, {'type': 'string'});
return extendedSchema;
}
function ContentSetting(contentType, settingSchema, schema) {
var getFunctionParameters = function(name) {
var f = $Array.filter(
schema.functions, function(f) { return f.name === name; })[0];
return f.parameters;
};
this.get = function(details, callback) {
var getSchema = getFunctionParameters('get');
validate([details, callback], getSchema);
var dummySetting = DEPRECATED_CONTENT_TYPES[contentType];
if (dummySetting !== undefined) {
console.warn('contentSettings.' + contentType + ' is deprecated; it will '
+ 'always return \'' + dummySetting + '\'.');
$Function.apply(callback, undefined, [{setting: dummySetting}]);
return;
}
return sendRequest('contentSettings.get',
[contentType, details, callback],
extendSchema(getSchema));
};
this.set = function(details, callback) {
// The set schema included in the Schema object is generic, since it varies
// per-setting. However, this is only ever for a single setting, so we can
// enforce the types more thoroughly.
var rawSetSchema = getFunctionParameters('set');
var rawSettingParam = rawSetSchema[0];
var props = $Object.assign({}, rawSettingParam.properties);
props.setting = settingSchema;
var modSettingParam = {
name: rawSettingParam.name,
type: rawSettingParam.type,
properties: props,
};
var modSetSchema = $Array.slice(rawSetSchema);
modSetSchema[0] = modSettingParam;
validate([details, callback], rawSetSchema);
if ($Object.hasOwnProperty(DEPRECATED_CONTENT_TYPES, contentType)) {
console.warn('contentSettings.' + contentType + ' is deprecated; setting '
+ 'it has no effect.');
$Function.apply(callback, undefined, []);
return;
}
return sendRequest('contentSettings.set',
[contentType, details, callback],
extendSchema(modSetSchema));
};
this.clear = function(details, callback) {
var clearSchema = getFunctionParameters('clear');
validate([details, callback], clearSchema);
if ($Object.hasOwnProperty(DEPRECATED_CONTENT_TYPES, contentType)) {
console.warn('contentSettings.' + contentType + ' is deprecated; '
+ 'clearing it has no effect.');
$Function.apply(callback, undefined, []);
return;
}
return sendRequest('contentSettings.clear',
[contentType, details, callback],
extendSchema(clearSchema));
};
this.getResourceIdentifiers = function(callback) {
var schema = getFunctionParameters('getResourceIdentifiers');
validate([callback], schema);
if ($Object.hasOwnProperty(DEPRECATED_CONTENT_TYPES, contentType)) {
$Function.apply(callback, undefined, []);
return;
}
return sendRequest(
'contentSettings.getResourceIdentifiers',
[contentType, callback],
extendSchema(schema));
};
}
exports.$set('ContentSetting', ContentSetting);
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the declarativeContent API.
var binding = require('binding').Binding.create('declarativeContent');
var utils = require('utils');
var validate = require('schemaUtils').validate;
var canonicalizeCompoundSelector =
requireNative('css_natives').CanonicalizeCompoundSelector;
var setIcon = require('setIcon').setIcon;
binding.registerCustomHook( function(api) {
var declarativeContent = api.compiledApi;
// Returns the schema definition of type |typeId| defined in |namespace|.
function getSchema(typeId) {
return utils.lookup(api.schema.types,
'id',
'declarativeContent.' + typeId);
}
// Helper function for the constructor of concrete datatypes of the
// declarative content API.
// Makes sure that |this| contains the union of parameters and
// {'instanceType': 'declarativeContent.' + typeId} and validates the
// generated union dictionary against the schema for |typeId|.
function setupInstance(instance, parameters, typeId) {
for (var key in parameters) {
if ($Object.hasOwnProperty(parameters, key)) {
instance[key] = parameters[key];
}
}
instance.instanceType = 'declarativeContent.' + typeId;
var schema = getSchema(typeId);
validate([instance], [schema]);
}
function canonicalizeCssSelectors(selectors) {
for (var i = 0; i < selectors.length; i++) {
var canonicalizedSelector = canonicalizeCompoundSelector(selectors[i]);
if (canonicalizedSelector == '') {
throw new Error(
'Element of \'css\' array must be a ' +
'list of valid compound selectors: ' +
selectors[i]);
}
selectors[i] = canonicalizedSelector;
}
}
// Setup all data types for the declarative content API.
declarativeContent.PageStateMatcher = function(parameters) {
setupInstance(this, parameters, 'PageStateMatcher');
if ($Object.hasOwnProperty(this, 'css')) {
canonicalizeCssSelectors(this.css);
}
};
declarativeContent.ShowPageAction = function(parameters) {
setupInstance(this, parameters, 'ShowPageAction');
};
declarativeContent.RequestContentScript = function(parameters) {
setupInstance(this, parameters, 'RequestContentScript');
};
// TODO(rockot): Do not expose this in M39 stable. Making this restriction
// possible will take some extra work. See http://crbug.com/415315
declarativeContent.SetIcon = function(parameters) {
setIcon(parameters, function (data) {
setupInstance(this, data, 'SetIcon');
}.bind(this));
};
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the desktopCapture API.
var binding = require('binding').Binding.create('desktopCapture');
var sendRequest = require('sendRequest').sendRequest;
var idGenerator = requireNative('id_generator');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
var pendingRequests = {};
function onRequestResult(id, result) {
if (id in pendingRequests) {
var callback = pendingRequests[id];
delete pendingRequests[id];
callback(result);
}
}
apiFunctions.setHandleRequest('chooseDesktopMedia',
function(sources, target_tab, callback) {
// |target_tab| is an optional parameter.
if (callback === undefined) {
callback = target_tab;
target_tab = undefined;
}
var id = idGenerator.GetNextId();
pendingRequests[id] = callback;
sendRequest(this.name,
[id, sources, target_tab, onRequestResult.bind(null, id)],
this.definition.parameters);
return id;
});
apiFunctions.setHandleRequest('cancelChooseDesktopMedia', function(id) {
if (id in pendingRequests) {
delete pendingRequests[id];
sendRequest(this.name, [id], this.definition.parameters);
}
});
});
exports.$set('binding', binding.generate());
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the developerPrivate API.
var binding = require('binding').Binding.create('developerPrivate');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
// Converts the argument of |functionName| from DirectoryEntry to URL.
function bindFileSystemFunction(functionName) {
apiFunctions.setUpdateArgumentsPostValidate(
functionName, function(directoryEntry, callback) {
var fileSystemName = directoryEntry.filesystem.name;
var relativePath = $String.slice(directoryEntry.fullPath, 1);
var url = directoryEntry.toURL();
return [fileSystemName, relativePath, url, callback];
});
}
bindFileSystemFunction('loadDirectory');
// developerPrivate.enable is the same as chrome.management.setEnabled.
// TODO(devlin): Migrate callers off developerPrivate.enable.
bindingsAPI.compiledApi.enable = chrome.management.setEnabled;
apiFunctions.setHandleRequest('allowFileAccess',
function(id, allow, callback) {
chrome.developerPrivate.updateExtensionConfiguration(
{extensionId: id, fileAccess: allow}, callback);
});
apiFunctions.setHandleRequest('allowIncognito',
function(id, allow, callback) {
chrome.developerPrivate.updateExtensionConfiguration(
{extensionId: id, incognitoAccess: allow}, callback);
});
apiFunctions.setHandleRequest('inspect', function(options, callback) {
var renderViewId = options.render_view_id;
if (typeof renderViewId == 'string') {
renderViewId = parseInt(renderViewId);
if (isNaN(renderViewId))
throw new Error('Invalid value for render_view_id');
}
var renderProcessId = options.render_process_id;
if (typeof renderProcessId == 'string') {
renderProcessId = parseInt(renderProcessId);
if (isNaN(renderProcessId))
throw new Error('Invalid value for render_process_id');
}
chrome.developerPrivate.openDevTools({
extensionId: options.extension_id,
renderProcessId: renderProcessId,
renderViewId: renderViewId,
incognito: options.incognito
}, callback);
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom bindings for the downloads API.
var binding = require('binding').Binding.create('downloads');
var downloadsInternal = require('binding').Binding.create(
'downloadsInternal').generate();
var eventBindings = require('event_bindings');
eventBindings.registerArgumentMassager(
'downloads.onDeterminingFilename',
function massage_determining_filename(args, dispatch) {
var downloadItem = args[0];
// Copy the id so that extensions can't change it.
var downloadId = downloadItem.id;
var suggestable = true;
function isValidResult(result) {
if (result === undefined)
return false;
if (typeof(result) != 'object') {
console.error('Error: Invocation of form suggest(' + typeof(result) +
') doesn\'t match definition suggest({filename: string, ' +
'conflictAction: string})');
return false;
} else if ((typeof(result.filename) != 'string') ||
(result.filename.length == 0)) {
console.error('Error: "filename" parameter to suggest() must be a ' +
'non-empty string');
return false;
} else if ([undefined, 'uniquify', 'overwrite', 'prompt'].indexOf(
result.conflictAction) < 0) {
console.error('Error: "conflictAction" parameter to suggest() must be ' +
'one of undefined, "uniquify", "overwrite", "prompt"');
return false;
}
return true;
}
function suggestCallback(result) {
if (!suggestable) {
console.error('suggestCallback may not be called more than once.');
return;
}
suggestable = false;
if (isValidResult(result)) {
downloadsInternal.determineFilename(
downloadId, result.filename, result.conflictAction || "");
} else {
downloadsInternal.determineFilename(downloadId, "", "");
}
}
try {
var results = dispatch([downloadItem, suggestCallback]);
var async = (results &&
results.results &&
(results.results.length != 0) &&
(results.results[0] === true));
if (suggestable && !async)
suggestCallback();
} catch (e) {
suggestCallback();
throw e;
}
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom bindings for the feedbackPrivate API.
var binding = require('binding').Binding.create('feedbackPrivate');
var blobNatives = requireNative('blob_natives');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setUpdateArgumentsPostValidate(
"sendFeedback", function(feedbackInfo, callback) {
var attachedFileBlobUuid = '';
var screenshotBlobUuid = '';
if (feedbackInfo.attachedFile)
attachedFileBlobUuid =
blobNatives.GetBlobUuid(feedbackInfo.attachedFile.data);
if (feedbackInfo.screenshot)
screenshotBlobUuid =
blobNatives.GetBlobUuid(feedbackInfo.screenshot);
feedbackInfo.attachedFileBlobUuid = attachedFileBlobUuid;
feedbackInfo.screenshotBlobUuid = screenshotBlobUuid;
return [feedbackInfo, callback];
});
});
exports.$set('binding', binding.generate());
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var fileSystemNatives = requireNative('file_system_natives');
var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem;
var sendRequest = require('sendRequest');
var lastError = require('lastError');
var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
// TODO(sammc): Don't require extension. See http://crbug.com/235689.
var GetExtensionViews = requireNative('runtime').GetExtensionViews;
// For a given |apiName|, generates object with two elements that are used
// in file system relayed APIs:
// * 'bindFileEntryCallback' function that provides mapping between JS objects
// into actual FileEntry|DirectoryEntry objects.
// * 'entryIdManager' object that implements methods for keeping the tracks of
// previously saved file entries.
function getFileBindingsForApi(apiName) {
// Fallback to using the current window if no background page is running.
var backgroundPage = GetExtensionViews(-1, -1, 'BACKGROUND')[0] || window;
var backgroundPageModuleSystem = GetModuleSystem(backgroundPage);
// All windows use the bindFileEntryCallback from the background page so their
// FileEntry objects have the background page's context as their own. This
// allows them to be used from other windows (including the background page)
// after the original window is closed.
if (window == backgroundPage) {
var bindFileEntryCallback = function(functionName, apiFunctions) {
apiFunctions.setCustomCallback(functionName,
function(name, request, callback, response) {
if (callback) {
if (!response) {
callback();
return;
}
var entries = [];
var hasError = false;
var getEntryError = function(fileError) {
if (!hasError) {
hasError = true;
lastError.run(
apiName + '.' + functionName,
'Error getting fileEntry, code: ' + fileError.code,
request.stack,
callback);
}
}
// Loop through the response entries and asynchronously get the
// FileEntry for each. We use hasError to ensure that only the first
// error is reported. Note that an error can occur either during the
// loop or in the asynchronous error callback to getFile.
$Array.forEach(response.entries, function(entry) {
if (hasError)
return;
var fileSystemId = entry.fileSystemId;
var baseName = entry.baseName;
var id = entry.id;
var fs = GetIsolatedFileSystem(fileSystemId);
try {
var getEntryCallback = function(fileEntry) {
if (hasError)
return;
entryIdManager.registerEntry(id, fileEntry);
entries.push(fileEntry);
// Once all entries are ready, pass them to the callback. In the
// event of an error, this condition will never be satisfied so
// the callback will not be called with any entries.
if (entries.length == response.entries.length) {
if (response.multiple) {
sendRequest.safeCallbackApply(
apiName + '.' + functionName, request, callback,
[entries]);
} else {
sendRequest.safeCallbackApply(
apiName + '.' + functionName, request, callback,
[entries[0]]);
}
}
}
// TODO(koz): fs.root.getFile() makes a trip to the browser
// process, but it might be possible avoid that by calling
// WebDOMFileSystem::createV8Entry().
if (entry.isDirectory) {
fs.root.getDirectory(baseName, {}, getEntryCallback,
getEntryError);
} else {
fs.root.getFile(baseName, {}, getEntryCallback, getEntryError);
}
} catch (e) {
if (!hasError) {
hasError = true;
lastError.run(apiName + '.' + functionName,
'Error getting fileEntry: ' + e.stack,
request.stack,
callback);
}
}
});
}
});
};
var entryIdManager = require('entryIdManager');
} else {
// Force the fileSystem API to be loaded in the background page. Using
// backgroundPageModuleSystem.require('fileSystem') is insufficient as
// requireNative is only allowed while lazily loading an API.
backgroundPage.chrome.fileSystem;
var bindFileEntryCallback = backgroundPageModuleSystem.require(
apiName).bindFileEntryCallback;
var entryIdManager = backgroundPageModuleSystem.require('entryIdManager');
}
return {bindFileEntryCallback: bindFileEntryCallback,
entryIdManager: entryIdManager};
}
exports.$set('getFileBindingsForApi', getFileBindingsForApi);
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the fileSystem API.
var binding = require('binding').Binding.create('fileSystem');
var sendRequest = require('sendRequest');
var getFileBindingsForApi =
require('fileEntryBindingUtil').getFileBindingsForApi;
var fileBindings = getFileBindingsForApi('fileSystem');
var bindFileEntryCallback = fileBindings.bindFileEntryCallback;
var entryIdManager = fileBindings.entryIdManager;
var fileSystemNatives = requireNative('file_system_natives');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
var fileSystem = bindingsAPI.compiledApi;
function bindFileEntryFunction(functionName) {
apiFunctions.setUpdateArgumentsPostValidate(
functionName, function(fileEntry, callback) {
var fileSystemName = fileEntry.filesystem.name;
var relativePath = $String.slice(fileEntry.fullPath, 1);
return [fileSystemName, relativePath, callback];
});
}
$Array.forEach(['getDisplayPath', 'getWritableEntry', 'isWritableEntry'],
bindFileEntryFunction);
$Array.forEach(['getWritableEntry', 'chooseEntry', 'restoreEntry'],
function(functionName) {
bindFileEntryCallback(functionName, apiFunctions);
});
apiFunctions.setHandleRequest('retainEntry', function(fileEntry) {
var id = entryIdManager.getEntryId(fileEntry);
if (!id)
return '';
var fileSystemName = fileEntry.filesystem.name;
var relativePath = $String.slice(fileEntry.fullPath, 1);
sendRequest.sendRequest(this.name, [id, fileSystemName, relativePath],
this.definition.parameters);
return id;
});
apiFunctions.setHandleRequest('isRestorable',
function(id, callback) {
var savedEntry = entryIdManager.getEntryById(id);
if (savedEntry) {
sendRequest.safeCallbackApply(
'fileSystem.isRestorable',
{},
callback,
[true]);
} else {
sendRequest.sendRequest(
this.name, [id, callback], this.definition.parameters);
}
});
apiFunctions.setUpdateArgumentsPostValidate('restoreEntry',
function(id, callback) {
var savedEntry = entryIdManager.getEntryById(id);
if (savedEntry) {
// We already have a file entry for this id so pass it to the callback and
// send a request to the browser to move it to the back of the LRU.
sendRequest.safeCallbackApply(
'fileSystem.restoreEntry',
{},
callback,
[savedEntry]);
return [id, false, null];
} else {
// Ask the browser process for a new file entry for this id, to be passed
// to |callback|.
return [id, true, callback];
}
});
apiFunctions.setCustomCallback('requestFileSystem',
function(name, request, callback, response) {
var fileSystem;
if (response && response.file_system_id) {
fileSystem = fileSystemNatives.GetIsolatedFileSystem(
response.file_system_id, response.file_system_path);
}
sendRequest.safeCallbackApply(
'fileSystem.requestFileSystem',
request,
callback,
[fileSystem]);
});
// TODO(benwells): Remove these deprecated versions of the functions.
fileSystem.getWritableFileEntry = function() {
console.log("chrome.fileSystem.getWritableFileEntry is deprecated");
console.log("Please use chrome.fileSystem.getWritableEntry instead");
$Function.apply(fileSystem.getWritableEntry, this, arguments);
};
fileSystem.isWritableFileEntry = function() {
console.log("chrome.fileSystem.isWritableFileEntry is deprecated");
console.log("Please use chrome.fileSystem.isWritableEntry instead");
$Function.apply(fileSystem.isWritableEntry, this, arguments);
};
fileSystem.chooseFile = function() {
console.log("chrome.fileSystem.chooseFile is deprecated");
console.log("Please use chrome.fileSystem.chooseEntry instead");
$Function.apply(fileSystem.chooseEntry, this, arguments);
};
});
exports.$set('bindFileEntryCallback', bindFileEntryCallback);
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the GCM API.
var binding = require('binding').Binding.create('gcm');
var forEach = require('utils').forEach;
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
var gcm = bindingsAPI.compiledApi;
apiFunctions.setUpdateArgumentsPostValidate(
'send', function(message, callback) {
// Validate message.data.
var payloadSize = 0;
forEach(message.data, function(property, value) {
if (property.length == 0)
throw new Error("One of data keys is empty.");
var lowerCasedProperty = property.toLowerCase();
// Issue an error for forbidden prefixes of property names.
if (lowerCasedProperty.startsWith("goog.") ||
lowerCasedProperty.startsWith("google") ||
property.startsWith("collapse_key")) {
throw new Error("Invalid data key: " + property);
}
payloadSize += property.length + value.length;
});
if (payloadSize > gcm.MAX_MESSAGE_SIZE)
throw new Error("Payload exceeded allowed size limit. Payload size is: "
+ payloadSize);
if (payloadSize == 0)
throw new Error("No data to send.");
return arguments;
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Identity API.
var binding = require('binding').Binding.create('identity');
binding.registerCustomHook(function(binding, id, contextType) {
var apiFunctions = binding.apiFunctions;
var identity = binding.compiledApi;
apiFunctions.setHandleRequest('getRedirectURL', function(path) {
if (path === null || path === undefined)
path = '/';
else
path = String(path);
if (path[0] != '/')
path = '/' + path;
return 'https://' + id + '.chromiumapp.org' + path;
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the image writer private API.
var binding = require('binding').Binding.create('imageWriterPrivate');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setUpdateArgumentsPostValidate(
'writeFromFile', function(device, fileEntry, options, callback) {
var fileSystemName = fileEntry.filesystem.name;
var relativePath = $String.slice(fileEntry.fullPath, 1);
return [device, fileSystemName, relativePath, callback];
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the input ime API. Only injected into the
// v8 contexts for extensions which have permission for the API.
var binding = require('binding').Binding.create('input.ime');
var Event = require('event_bindings').Event;
var appWindowNatives = requireNative('app_window_natives');
binding.registerCustomHook(function(api) {
var input_ime = api.compiledApi;
input_ime.onKeyEvent.dispatchToListener = function(callback, args) {
var engineID = args[0];
var keyData = args[1];
var result = false;
try {
result = $Function.call(Event.prototype.dispatchToListener,
this, callback, args);
} catch (e) {
console.error('Error in event handler for onKeyEvent: ' + e.stack);
}
if (!input_ime.onKeyEvent.async) {
input_ime.keyEventHandled(keyData.requestId, result);
}
};
input_ime.onKeyEvent.addListener = function(cb, opt_extraInfo) {
input_ime.onKeyEvent.async = false;
if (opt_extraInfo instanceof Array) {
for (var i = 0; i < opt_extraInfo.length; ++i) {
if (opt_extraInfo[i] == "async") {
input_ime.onKeyEvent.async = true;
}
}
}
$Function.call(Event.prototype.addListener, this, cb);
};
api.apiFunctions.setCustomCallback('createWindow',
function(name, request, callback, windowParams) {
if (!callback) {
return;
}
var view;
if (windowParams && windowParams.frameId) {
view = appWindowNatives.GetFrame(
windowParams.frameId, false /* notifyBrowser */);
view.id = windowParams.frameId;
}
callback(view);
});
});
exports.$set('binding', binding.generate());
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the logPrivate API.
var binding = require('binding').Binding.create('logPrivate');
var sendRequest = require('sendRequest');
var getFileBindingsForApi =
require('fileEntryBindingUtil').getFileBindingsForApi;
var fileBindings = getFileBindingsForApi('logPrivate');
var bindFileEntryCallback = fileBindings.bindFileEntryCallback;
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
var fileSystem = bindingsAPI.compiledApi;
$Array.forEach(['dumpLogs'],
function(functionName) {
bindFileEntryCallback(functionName, apiFunctions);
});
});
exports.$set('bindFileEntryCallback', bindFileEntryCallback);
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Media Gallery API.
var binding = require('binding').Binding.create('mediaGalleries');
var blobNatives = requireNative('blob_natives');
var mediaGalleriesNatives = requireNative('mediaGalleries');
var blobsAwaitingMetadata = {};
var mediaGalleriesMetadata = {};
function createFileSystemObjectsAndUpdateMetadata(response) {
var result = [];
mediaGalleriesMetadata = {}; // Clear any previous metadata.
if (response) {
for (var i = 0; i < response.length; i++) {
var filesystem = mediaGalleriesNatives.GetMediaFileSystemObject(
response[i].fsid);
$Array.push(result, filesystem);
var metadata = response[i];
delete metadata.fsid;
mediaGalleriesMetadata[filesystem.name] = metadata;
}
}
return result;
}
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
// getMediaFileSystems and addUserSelectedFolder use a custom callback so that
// they can instantiate and return an array of file system objects.
apiFunctions.setCustomCallback('getMediaFileSystems',
function(name, request, callback, response) {
var result = createFileSystemObjectsAndUpdateMetadata(response);
if (callback)
callback(result);
});
apiFunctions.setCustomCallback('addUserSelectedFolder',
function(name, request, callback, response) {
var fileSystems = [];
var selectedFileSystemName = "";
if (response && 'mediaFileSystems' in response &&
'selectedFileSystemIndex' in response) {
fileSystems = createFileSystemObjectsAndUpdateMetadata(
response['mediaFileSystems']);
var selectedFileSystemIndex = response['selectedFileSystemIndex'];
if (selectedFileSystemIndex >= 0) {
selectedFileSystemName = fileSystems[selectedFileSystemIndex].name;
}
}
if (callback)
callback(fileSystems, selectedFileSystemName);
});
apiFunctions.setHandleRequest('getMediaFileSystemMetadata',
function(filesystem) {
if (filesystem && filesystem.name &&
filesystem.name in mediaGalleriesMetadata) {
return mediaGalleriesMetadata[filesystem.name];
}
return {
'name': '',
'galleryId': '',
'isRemovable': false,
'isMediaDevice': false,
'isAvailable': false,
};
});
apiFunctions.setUpdateArgumentsPostValidate('getMetadata',
function(mediaFile, options, callback) {
var blobUuid = blobNatives.GetBlobUuid(mediaFile)
// Store the blob in a global object to keep its refcount nonzero -- this
// prevents the object from being garbage collected before any metadata
// parsing gets to occur (see crbug.com/415792).
blobsAwaitingMetadata[blobUuid] = mediaFile;
return [blobUuid, options, callback];
});
apiFunctions.setCustomCallback('getMetadata',
function(name, request, callback, response) {
if (response && response.attachedImagesBlobInfo) {
for (var i = 0; i < response.attachedImagesBlobInfo.length; i++) {
var blobInfo = response.attachedImagesBlobInfo[i];
var blob = blobNatives.TakeBrowserProcessBlob(
blobInfo.blobUUID, blobInfo.type, blobInfo.size);
response.metadata.attachedImages.push(blob);
}
}
if (callback)
callback(response ? response.metadata : null);
// The UUID was in position 0 in the setUpdateArgumentsPostValidate
// function.
var uuid = request.args[0];
delete blobsAwaitingMetadata[uuid];
});
});
exports.$set('binding', binding.generate());
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom bindings for the notifications API.
//
var binding = require('binding').Binding.create('notifications');
var sendRequest = require('sendRequest').sendRequest;
var exceptionHandler = require('uncaught_exception_handler');
var imageUtil = require('imageUtil');
var lastError = require('lastError');
var notificationsPrivate = requireNative('notifications_private');
function imageDataSetter(context, key) {
var f = function(val) {
this[key] = val;
};
return $Function.bind(f, context);
}
// A URL Spec is an object with the following keys:
// path: The resource to be downloaded.
// width: (optional) The maximum width of the image to be downloaded in device
// pixels.
// height: (optional) The maximum height of the image to be downloaded in
// device pixels.
// callback: A function to be called when the URL is complete. It
// should accept an ImageData object and set the appropriate
// field in |notificationDetails|.
function getUrlSpecs(imageSizes, notificationDetails) {
var urlSpecs = [];
// |iconUrl| might be optional for notification updates.
if (notificationDetails.iconUrl) {
$Array.push(urlSpecs, {
path: notificationDetails.iconUrl,
width: imageSizes.icon.width * imageSizes.scaleFactor,
height: imageSizes.icon.height * imageSizes.scaleFactor,
callback: imageDataSetter(notificationDetails, 'iconBitmap')
});
}
// |appIconMaskUrl| is optional.
if (notificationDetails.appIconMaskUrl) {
$Array.push(urlSpecs, {
path: notificationDetails.appIconMaskUrl,
width: imageSizes.appIconMask.width * imageSizes.scaleFactor,
height: imageSizes.appIconMask.height * imageSizes.scaleFactor,
callback: imageDataSetter(notificationDetails, 'appIconMaskBitmap')
});
}
// |imageUrl| is optional.
if (notificationDetails.imageUrl) {
$Array.push(urlSpecs, {
path: notificationDetails.imageUrl,
width: imageSizes.image.width * imageSizes.scaleFactor,
height: imageSizes.image.height * imageSizes.scaleFactor,
callback: imageDataSetter(notificationDetails, 'imageBitmap')
});
}
// Each button has an optional icon.
var buttonList = notificationDetails.buttons;
if (buttonList && typeof buttonList.length === 'number') {
var numButtons = buttonList.length;
for (var i = 0; i < numButtons; i++) {
if (buttonList[i].iconUrl) {
$Array.push(urlSpecs, {
path: buttonList[i].iconUrl,
width: imageSizes.buttonIcon.width * imageSizes.scaleFactor,
height: imageSizes.buttonIcon.height * imageSizes.scaleFactor,
callback: imageDataSetter(buttonList[i], 'iconBitmap')
});
}
}
}
return urlSpecs;
}
function replaceNotificationOptionURLs(notification_details, callback) {
var imageSizes = notificationsPrivate.GetNotificationImageSizes();
var url_specs = getUrlSpecs(imageSizes, notification_details);
if (!url_specs.length) {
callback(true);
return;
}
var errors = 0;
imageUtil.loadAllImages(url_specs, {
onerror: function(index) {
errors++;
},
oncomplete: function(imageData) {
if (errors > 0) {
callback(false);
return;
}
for (var index = 0; index < url_specs.length; index++) {
var url_spec = url_specs[index];
url_spec.callback(imageData[index]);
}
callback(true);
}
});
}
function genHandle(name, failure_function) {
return function(id, input_notification_details, callback) {
// TODO(dewittj): Remove this hack. This is used as a way to deep
// copy a complex JSON object.
var notification_details = $JSON.parse(
$JSON.stringify(input_notification_details));
var that = this;
var stack = exceptionHandler.getExtensionStackTrace();
replaceNotificationOptionURLs(notification_details, function(success) {
if (success) {
sendRequest(that.name,
[id, notification_details, callback],
that.definition.parameters, {__proto__: null, stack: stack});
return;
}
lastError.run(name,
'Unable to download all specified images.',
stack,
failure_function, [callback || function() {}, id]);
});
};
}
var handleCreate = genHandle('notifications.create',
function(callback, id) { callback(id); });
var handleUpdate = genHandle('notifications.update',
function(callback, id) { callback(false); });
var notificationsCustomHook = function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('create', handleCreate);
apiFunctions.setHandleRequest('update', handleUpdate);
};
binding.registerCustomHook(notificationsCustomHook);
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the omnibox API. Only injected into the v8 contexts
// for extensions which have permission for the omnibox API.
var binding = require('binding').Binding.create('omnibox');
var eventBindings = require('event_bindings');
var sendRequest = require('sendRequest').sendRequest;
// Remove invalid characters from |text| so that it is suitable to use
// for |AutocompleteMatch::contents|.
function sanitizeString(text, shouldTrim) {
// NOTE: This logic mirrors |AutocompleteMatch::SanitizeString()|.
// 0x2028 = line separator; 0x2029 = paragraph separator.
var kRemoveChars = /(\r|\n|\t|\u2028|\u2029)/gm;
if (shouldTrim)
text = text.trimLeft();
return text.replace(kRemoveChars, '');
}
// Parses the xml syntax supported by omnibox suggestion results. Returns an
// object with two properties: 'description', which is just the text content,
// and 'descriptionStyles', which is an array of style objects in a format
// understood by the C++ backend.
function parseOmniboxDescription(input) {
var domParser = new DOMParser();
// The XML parser requires a single top-level element, but we want to
// support things like 'hello, <match>world</match>!'. So we wrap the
// provided text in generated root level element.
var root = domParser.parseFromString(
'<fragment>' + input + '</fragment>', 'text/xml');
// DOMParser has a terrible error reporting facility. Errors come out nested
// inside the returned document.
var error = root.querySelector('parsererror div');
if (error) {
throw new Error(error.textContent);
}
// Otherwise, it's valid, so build up the result.
var result = {
description: '',
descriptionStyles: []
};
// Recursively walk the tree.
function walk(node) {
for (var i = 0, child; child = node.childNodes[i]; i++) {
// Append text nodes to our description.
if (child.nodeType == Node.TEXT_NODE) {
var shouldTrim = result.description.length == 0;
result.description += sanitizeString(child.nodeValue, shouldTrim);
continue;
}
// Process and descend into a subset of recognized tags.
if (child.nodeType == Node.ELEMENT_NODE &&
(child.nodeName == 'dim' || child.nodeName == 'match' ||
child.nodeName == 'url')) {
var style = {
'type': child.nodeName,
'offset': result.description.length
};
$Array.push(result.descriptionStyles, style);
walk(child);
style.length = result.description.length - style.offset;
continue;
}
// Descend into all other nodes, even if they are unrecognized, for
// forward compat.
walk(child);
}
};
walk(root);
return result;
}
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setUpdateArgumentsPreValidate('setDefaultSuggestion',
function(suggestResult) {
if (suggestResult.content != undefined) { // null, etc.
throw new Error(
'setDefaultSuggestion cannot contain the "content" field');
}
return [suggestResult];
});
apiFunctions.setHandleRequest('setDefaultSuggestion', function(details) {
var parseResult = parseOmniboxDescription(details.description);
sendRequest(this.name, [parseResult], this.definition.parameters);
});
apiFunctions.setUpdateArgumentsPostValidate(
'sendSuggestions', function(requestId, userSuggestions) {
var suggestions = [];
for (var i = 0; i < userSuggestions.length; i++) {
var parseResult = parseOmniboxDescription(
userSuggestions[i].description);
parseResult.content = userSuggestions[i].content;
$Array.push(suggestions, parseResult);
}
return [requestId, suggestions];
});
});
eventBindings.registerArgumentMassager('omnibox.onInputChanged',
function(args, dispatch) {
var text = args[0];
var requestId = args[1];
var suggestCallback = function(suggestions) {
chrome.omnibox.sendSuggestions(requestId, suggestions);
};
dispatch([text, suggestCallback]);
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the pageAction API.
var binding = require('binding').Binding.create('pageAction');
var setIcon = require('setIcon').setIcon;
var sendRequest = require('sendRequest').sendRequest;
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('setIcon', function(details, callback) {
setIcon(details, function(args) {
sendRequest(this.name, [args, callback], this.definition.parameters);
}.bind(this));
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the pageCapture API.
var binding = require('binding').Binding.create('pageCapture');
var handleUncaughtException = require('uncaught_exception_handler').handle;
var pageCaptureNatives = requireNative('page_capture');
var CreateBlob = pageCaptureNatives.CreateBlob;
var SendResponseAck = pageCaptureNatives.SendResponseAck;
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setCustomCallback('saveAsMHTML',
function(name, request, callback, response) {
if (response)
response = CreateBlob(response.mhtmlFilePath, response.mhtmlFileLength);
try {
callback(response);
} catch (e) {
handleUncaughtException(
'Error in chrome.pageCapture.saveAsMHTML callback', e, request.stack);
} finally {
// Notify the browser. Now that the blob is referenced from JavaScript,
// the browser can drop its reference to it.
SendResponseAck(request.id);
}
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the syncFileSystem API.
var binding = require('binding').Binding.create('syncFileSystem');
var eventBindings = require('event_bindings');
var fileSystemNatives = requireNative('file_system_natives');
var syncFileSystemNatives = requireNative('sync_file_system');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
// Functions which take in an [instanceOf=FileEntry].
function bindFileEntryFunction(functionName) {
apiFunctions.setUpdateArgumentsPostValidate(
functionName, function(entry, callback) {
var fileSystemUrl = entry.toURL();
return [fileSystemUrl, callback];
});
}
$Array.forEach(['getFileStatus'], bindFileEntryFunction);
// Functions which take in a FileEntry array.
function bindFileEntryArrayFunction(functionName) {
apiFunctions.setUpdateArgumentsPostValidate(
functionName, function(entries, callback) {
var fileSystemUrlArray = [];
for (var i=0; i < entries.length; i++) {
$Array.push(fileSystemUrlArray, entries[i].toURL());
}
return [fileSystemUrlArray, callback];
});
}
$Array.forEach(['getFileStatuses'], bindFileEntryArrayFunction);
// Functions which take in an [instanceOf=DOMFileSystem].
function bindFileSystemFunction(functionName) {
apiFunctions.setUpdateArgumentsPostValidate(
functionName, function(filesystem, callback) {
var fileSystemUrl = filesystem.root.toURL();
return [fileSystemUrl, callback];
});
}
$Array.forEach(['getUsageAndQuota'], bindFileSystemFunction);
// Functions which return an [instanceOf=DOMFileSystem].
apiFunctions.setCustomCallback('requestFileSystem',
function(name, request, callback, response) {
var result = null;
if (response) {
result = syncFileSystemNatives.GetSyncFileSystemObject(
response.name, response.root);
}
if (callback)
callback(result);
});
// Functions which return an array of FileStatusInfo object
// which has [instanceOf=FileEntry].
apiFunctions.setCustomCallback('getFileStatuses',
function(name, request, callback, response) {
var results = [];
if (response) {
for (var i = 0; i < response.length; i++) {
var result = {};
var entry = response[i].entry;
result.fileEntry = fileSystemNatives.GetFileEntry(
entry.fileSystemType,
entry.fileSystemName,
entry.rootUrl,
entry.filePath,
entry.isDirectory);
result.status = response[i].status;
result.error = response[i].error;
$Array.push(results, result);
}
}
if (callback)
callback(results);
});
});
eventBindings.registerArgumentMassager(
'syncFileSystem.onFileStatusChanged', function(args, dispatch) {
// Make FileEntry object using all the base string fields.
var fileEntry = fileSystemNatives.GetFileEntry(
args[0].fileSystemType,
args[0].fileSystemName,
args[0].rootUrl,
args[0].filePath,
args[0].isDirectory);
// Combine into a single dictionary.
var fileInfo = new Object();
fileInfo.fileEntry = fileEntry;
fileInfo.status = args[1];
if (fileInfo.status == "synced") {
fileInfo.action = args[2];
fileInfo.direction = args[3];
}
dispatch([fileInfo]);
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the systemIndicator API.
// TODO(dewittj) Refactor custom binding to reduce redundancy between the
// extension action APIs.
var binding = require('binding').Binding.create('systemIndicator');
var setIcon = require('setIcon').setIcon;
var sendRequest = require('sendRequest').sendRequest;
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setHandleRequest('setIcon', function(details, callback) {
setIcon(details, function(args) {
sendRequest(this.name, [args, callback], this.definition.parameters);
}.bind(this));
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the Tab Capture API.
var binding = require('binding').Binding.create('tabCapture');
var lastError = require('lastError');
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
function proxyToGetUserMedia(name, request, callback, response) {
if (!callback)
return;
if (!response) {
// When the response is missing, runtime.lastError has already been set.
// See chrome/browser/extensions/api/tab_capture/tab_capture_api.cc.
callback(null);
return;
}
// Convenience function for processing webkitGetUserMedia() error objects to
// provide runtime.lastError messages for the tab capture API.
function getErrorMessage(error, fallbackMessage) {
if (!error || (typeof error.message != 'string'))
return fallbackMessage;
return error.message.replace(/(navigator\.)?(webkit)?GetUserMedia/gi,
name);
}
var options = {};
if (response.audioConstraints)
options.audio = response.audioConstraints;
if (response.videoConstraints)
options.video = response.videoConstraints;
try {
navigator.webkitGetUserMedia(
options,
function onSuccess(media_stream) {
callback(media_stream);
},
function onError(error) {
lastError.run(
name,
getErrorMessage(error, "Failed to start MediaStream."),
request.stack,
function() { callback(null); });
});
} catch (error) {
lastError.run(name,
getErrorMessage(error, "Invalid argument(s)."),
request.stack,
function() { callback(null); });
}
}
apiFunctions.setCustomCallback('capture', proxyToGetUserMedia);
apiFunctions.setCustomCallback('captureOffscreenTab', proxyToGetUserMedia);
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the tabs API.
var binding = require('binding').Binding.create('tabs');
var messaging = require('messaging');
var OpenChannelToTab = requireNative('messaging_natives').OpenChannelToTab;
var sendRequestIsDisabled = requireNative('process').IsSendRequestDisabled();
var forEach = require('utils').forEach;
binding.registerCustomHook(function(bindingsAPI, extensionId) {
var apiFunctions = bindingsAPI.apiFunctions;
var tabs = bindingsAPI.compiledApi;
apiFunctions.setHandleRequest('connect', function(tabId, connectInfo) {
var name = '';
var frameId = -1;
if (connectInfo) {
name = connectInfo.name || name;
frameId = connectInfo.frameId;
if (typeof frameId == 'undefined' || frameId < 0)
frameId = -1;
}
var portId = OpenChannelToTab(tabId, frameId, extensionId, name);
return messaging.createPort(portId, name);
});
apiFunctions.setHandleRequest('sendRequest',
function(tabId, request, responseCallback) {
if (sendRequestIsDisabled)
throw new Error(sendRequestIsDisabled);
var port = tabs.connect(tabId, {name: messaging.kRequestChannel});
messaging.sendMessageImpl(port, request, responseCallback);
});
apiFunctions.setHandleRequest('sendMessage',
function(tabId, message, options, responseCallback) {
var connectInfo = {
name: messaging.kMessageChannel
};
if (options) {
forEach(options, function(k, v) {
connectInfo[k] = v;
});
}
var port = tabs.connect(tabId, connectInfo);
messaging.sendMessageImpl(port, message, responseCallback);
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function watchForTag(tagName, cb) {
if (!document.body)
return;
function findChildTags(queryNode) {
$Array.forEach(queryNode.querySelectorAll(tagName), function(node) {
cb(node);
});
}
// Query tags already in the document.
findChildTags(document.body);
// Observe the tags added later.
var documentObserver = new MutationObserver(function(mutations) {
$Array.forEach(mutations, function(mutation) {
$Array.forEach(mutation.addedNodes, function(addedNode) {
if (addedNode.nodeType == Node.ELEMENT_NODE) {
if (addedNode.tagName == tagName)
cb(addedNode);
findChildTags(addedNode);
}
});
});
});
documentObserver.observe(document, {subtree: true, childList: true});
}
// Expose a function to watch the |tagName| introduction via mutation observer.
//
// We employee mutation observer to watch on any introduction of |tagName|
// within document so that we may handle it accordingly (either creating it or
// reporting error due to lack of permission).
// Think carefully about when to call this. On one hand, mutation observer
// functions on document, so we need to make sure document is finished
// parsing. To satisfy this, document.readyState has to be "interactive" or
// after. On the other hand, we intend to do this as early as possible so that
// developer would have no chance to bring in any conflicted property. To meet
// this requirement, we choose "readystatechange" event of window and use
// capturing way.
function addTagWatcher(tagName, cb) {
var useCapture = true;
window.addEventListener('readystatechange', function listener(event) {
if (document.readyState == 'loading')
return;
watchForTag(tagName, cb);
window.removeEventListener(event.type, listener, useCapture);
}, useCapture);
}
exports.$set('addTagWatcher', addTagWatcher);
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the tts API.
var binding = require('binding').Binding.create('tts');
var idGenerator = requireNative('id_generator');
var sendRequest = require('sendRequest').sendRequest;
var lazyBG = requireNative('lazy_background_page');
binding.registerCustomHook(function(api) {
var apiFunctions = api.apiFunctions;
var tts = api.compiledApi;
var handlers = {};
function ttsEventListener(event) {
var eventHandler = handlers[event.srcId];
if (eventHandler) {
eventHandler({
type: event.type,
charIndex: event.charIndex,
errorMessage: event.errorMessage
});
if (event.isFinalEvent) {
delete handlers[event.srcId];
// Balanced in 'speak' handler.
lazyBG.DecrementKeepaliveCount();
}
}
}
// This file will get run if an extension needs the ttsEngine permission, but
// it doesn't necessarily have the tts permission. If it doesn't, trying to
// add a listener to chrome.tts.onEvent will fail.
// See http://crbug.com/122474.
try {
tts.onEvent.addListener(ttsEventListener);
} catch (e) {}
apiFunctions.setHandleRequest('speak', function() {
var args = arguments;
if (args.length > 1 && args[1] && args[1].onEvent) {
var id = idGenerator.GetNextId();
args[1].srcId = id;
handlers[id] = args[1].onEvent;
// Keep the page alive until the event finishes.
// Balanced in eventHandler.
lazyBG.IncrementKeepaliveCount();
}
sendRequest(this.name, args, this.definition.parameters);
return id;
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the ttsEngine API.
var binding = require('binding').Binding.create('ttsEngine');
var eventBindings = require('event_bindings');
eventBindings.registerArgumentMassager('ttsEngine.onSpeak',
function(args, dispatch) {
var text = args[0];
var options = args[1];
var requestId = args[2];
var sendTtsEvent = function(event) {
chrome.ttsEngine.sendTtsEvent(requestId, event);
};
dispatch([text, options, sendTtsEvent]);
});
exports.$set('binding', binding.generate());
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the webrtcDesktopCapturePrivate API.
var binding = require('binding').Binding.create('webrtcDesktopCapturePrivate');
var sendRequest = require('sendRequest').sendRequest;
var idGenerator = requireNative('id_generator');
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
var pendingRequests = {};
function onRequestResult(id, result) {
if (id in pendingRequests) {
var callback = pendingRequests[id];
delete pendingRequests[id];
callback(result);
}
}
apiFunctions.setHandleRequest('chooseDesktopMedia',
function(sources, request, callback) {
var id = idGenerator.GetNextId();
pendingRequests[id] = callback;
sendRequest(this.name,
[id, sources, request, onRequestResult.bind(null, id)],
this.definition.parameters);
return id;
});
apiFunctions.setHandleRequest('cancelChooseDesktopMedia', function(id) {
if (id in pendingRequests) {
delete pendingRequests[id];
sendRequest(this.name, [id], this.definition.parameters);
}
});
});
exports.$set('binding', binding.generate());
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the webstore API.
var webstoreNatives = requireNative('webstore');
var Event = require('event_bindings').Event;
function Installer() {
this._pendingInstall = null;
this.onInstallStageChanged =
new Event(null, [{name: 'stage', type: 'string'}], {unmanaged: true});
this.onDownloadProgress =
new Event(null, [{name: 'progress', type: 'number'}], {unmanaged: true});
}
Installer.prototype.install = function(url, onSuccess, onFailure) {
if (this._pendingInstall)
throw new Error('A Chrome Web Store installation is already pending.');
if (url !== undefined && typeof(url) !== 'string') {
throw new Error(
'The Chrome Web Store item link URL parameter must be a string.');
}
if (onSuccess !== undefined && typeof(onSuccess) !== 'function')
throw new Error('The success callback parameter must be a function.');
if (onFailure !== undefined && typeof(onFailure) !== 'function')
throw new Error('The failure callback parameter must be a function.');
// Since we call Install() with a bool for if we have listeners, listeners
// must be set prior to the inline installation starting (this is also
// noted in the Event documentation in
// chrome/common/extensions/api/webstore.json).
var installId = webstoreNatives.Install(
this.onInstallStageChanged.hasListeners(),
this.onDownloadProgress.hasListeners(),
url,
onSuccess,
onFailure);
if (installId !== undefined) {
this._pendingInstall = {
installId: installId,
onSuccess: onSuccess,
onFailure: onFailure
};
}
};
Installer.prototype.onInstallResponse =
function(installId, success, error, resultCode) {
var pendingInstall = this._pendingInstall;
if (!pendingInstall || pendingInstall.installId != installId) {
// TODO(kalman): should this be an error?
return;
}
try {
if (success && pendingInstall.onSuccess)
pendingInstall.onSuccess();
else if (!success && pendingInstall.onFailure)
pendingInstall.onFailure(error, resultCode);
} catch (e) {
console.error('Exception in chrome.webstore.install response handler: ' +
e.stack);
} finally {
this._pendingInstall = null;
}
};
Installer.prototype.onInstallStageChanged = function(installStage) {
this.onInstallStageChanged.dispatch(installStage);
};
Installer.prototype.onDownloadProgress = function(progress) {
this.onDownloadProgress.dispatch(progress);
};
var installer = new Installer();
var chromeWebstore = {
install: function (url, onSuccess, onFailure) {
installer.install(url, onSuccess, onFailure);
},
onInstallStageChanged: installer.onInstallStageChanged,
onDownloadProgress: installer.onDownloadProgress
};
exports.$set('binding', chromeWebstore);
// Called by webstore_bindings.cc.
exports.onInstallResponse =
Installer.prototype.onInstallResponse.bind(installer);
exports.onInstallStageChanged =
Installer.prototype.onInstallStageChanged.bind(installer);
exports.onDownloadProgress =
Installer.prototype.onDownloadProgress.bind(installer);
PNG
IHDR * * oÊÐ `IDATx^í×1
CÑÜÿÒ
nò.
´3(¥¼Aé $ހìÉC;Ëߎ DŽ,`ºíN3QlÃyJ ÝÿúAõg
uJ!îC7 £'*膟.Msß* IEND®B`PNG
IHDR J A ÌuÏ µIDATx^µÓQj1@QÁ¸î-º!áý
·¯#©#¥ÓK¾($y ,;Ùltó¿YvÓÙBz¦«ÀOó(#DQ6«&«}úɝE'¥Ú̲®¦°Ú,9VF|۲á&ÓÑòï&Ýä~¯«½!jY͑
Èa¶¼RoFZ7'Ê!fg¿7m¼>wÓ%Zëá§Ùa--4
çȿõޤI]Ï IEND®B`PNG
IHDR có¨ 5IDATxՕËjAϩK§3Ã\`.²qe\Ü ¾o'¾
{
b,1I撮ªsq¦@IYôHþê¢ûÿ8ç.TU¸K¸c9¸ENâွA MIõ2Ií'>"Ä&/ËlI^=ê>ïuëüÏÅ qê+'$_ñ`RH.&OäBÚêø¨3"XXl83oq)'{ªÚrØ-¬8+ÖN3g ×?Ç,P½þJ~®AR¬S/À(Z±"fõI³É'³ ½õý
I±»̀ÜÓEO<{â³Ëj-æ`DÎÂFË8vܨT´³\
âýy»Ñò+¥ÚL¬֛æ4ÈZÃ>[ñW¤÷Jóbµ Õ9<¦µùÓ˰ý !g~|÷}<1}ÜöÝ9YP"ùýÞY¼Ùk×ÑqÅûWioæ|<
¶ãQª¢\cÓâjÓ_¢!d&¦@Çd@aw¹`E\ZpK
Y._~[[âǯñë'KÄõGdmàíçØpæIJX )T¼ËS*"ov7k³ !BøsbYK"6<幘óX féÀÇt)øoµ5»Ï;Ðl0YSO Eÿéwý²×^)MÞb®¢&òõ
÷í¸Eÿÿökui`bóóJ IEND®B`PNG
IHDR có¨ ºIDATxìAÄ0Äöÿÿ
5dR¢¾¡§%3òo?|_à=ۀB«VsfVUD4´4Æðދ¿g $GH$ÝÏ
½½4łwïÞõtuådf-Y´øÄñsfϞ7gNVFƶ[oݺOKM»ùüý¦¨G©:O·M8hV[k묙3s³²ê.YÛÝÙyôÈÑϟ?£XðôɓæƦCfe¤·67×TT¦¥¤̙3çÐÁC
µõûöî}ñêÍÕö¡¢CEÞĊÈ1¯®mX³zumUõÔÉSª+*:´pB¼윆ں+/£ÑÓÇOښ?zÔÞÚZ]YYUQ 4´²¼",(¸º²êѣG@5罹=}(þ.[àýt
ÚXYoݺe͚5±QQú'̙9ÓÔÈ('+\è¼x$9!±¡®nýÚuõ
Í
@¥%EÅ;·ïHIJÊË+ؽpʻ«|_gý¼÷ì¹˱QÑ<ؾ}{Bl\o߂yó+ËʁpíÚ5tñs÷ΝǏmúòåËãǏ¯\¾Êׯ^ýþõëÔɓGyöòõ7þ><òï-Pÿ?`Ä>{ö
ï߿?wöÜÙ3gz_¾|ùìéS`Ò%þùñûôbXðïǧ?·÷}ôµEÊFyóçqKã¾6+}mûڤÀ@5£ßÜûse㏕é_[¿ÖK JÍ}÷è÷¹e?Öæ~l÷µ^òkä×Fy ¹Zðïë»?ÿÜPø}¦ç×Nݯµâ_륿6A&ׂ¿¿ÿ>¿üûø¬Ë¾M¶ûÖmøµAèd 7t~~ùóðô¯£Ó®Îø6Ñêk§ö×VUP 4È|mÅ!A wOËo²¯ IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR có¨ IDATxcøOc@£|=wîavö5SÓËjj7µ¶þ~ý$ñýÍÿsÿ79ü_¡öÉÿÃÙÿ_#͂¿?ÊË;ÃÈxççÿº¡ëÿ<¾ÿ3Pãÿ#¹ÿÿþ&ւÑ-à½gÎô¹l(&:A¹ò>ócxæÏðw
;&lÁý}¡ÉG´O&çM>ÍÊzÓÍáu}jf=¿¤ìõRdÓ̴þs(°ÇõLîÜyypñK.®}rÆò.è:¾fÿ¾9NN\phùµ³7nZ¿¾.ôЦ-W7O¾wjûÉ={ïÜ|øo³+aVû:xåø3ÇVîZsv穵ӷní[¾:·íË˧ÙxlÃÂíÿW¨oÚxpÇÑSûn5íÚæ¾ó'.þ_OåEóÂË憖Ì/Yݶxy~ÏúåÓÖǗhZ°:«~Stöݫ7Ìm¨^?oÉæ}gÛ6.X¶cVçÿ)-ø¸{7Ö>ËÁñ<÷Y ÃëXX¸c¢ǻJ¦��Úq]_õu,ãÏv¦ïú÷û+Qüýñã^l,Opr¾nMú?é1ÿÿü ¨ø´o߽¨¨Ë**DE¯êë?.)ùñà8§<ü¼Eÿ/W:üÿÓ}·4µ ±+8¸rY IEND®B`PNG
IHDR có¨ @IDATxcøOc0¢,µàϟ¿ë7mëÙÒõÄé»ÁkN¹¿mëÙÚֻ(òýǯUëÏַo©oÛzìä=²ۯNuäԹDŽ}°{ßUuêi@öçÏß
¬êíÜÛ߽û¢mZ7óï߿á ³t-ý£fx
N»ø8PٍÛ/Í\'YzLmßETY;·ê×þþýgùêFUgì9sþЂúu7o¿Ð6kð úë× J Y߱ÃÆsoÔüàø%DYPR½Rèò굧¡±SuÌj_½ú(-ÓÚ9 ÃÇ+d²®E³w贃Go¿}÷ÕÚcRyÃÖƮÝVӁ\¬ÛtVð²¹s0¬*W EZº6}pàðM ûý¯ÀÈ0´m7²ë¸}÷լÇ̜'ì=t{ùÚ֞Ó¿OW¯?Ö6©ÏÙó"s>xüôݍ[ρi(¿Ì&ãÛîÁ3»N²p
ôÁÌ'J¦~a¦FNpmÝڍmÿýûçäÝcb×jéܥoݺ`Ö&N½¥u7l½¼aëG¿Yy²à䙻+V¼rí)»iëù}¯ûݘ·øÈü%Ç»´ïȉ{«7ùêDٮý·¶í¾I_¿ýüðáۏ¿!ÜO¾ùòp¿üx÷þËû߾|ý ´àë·_?}Xöç§Ï?F^Y4j ×8ާó IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR Éú` vIDATWÊ=@@P؝«¸
+HÆO« ²7P° �DW¿äxü¼T֫°äÖ ʝ
3Ë'Êzu9ë@¡8¤YŁeÂqèп á"4+hxRÁÖ *
^ª@çU¸õ=ú9ûéÉ4 IEND®B`PNG
IHDR ì?O ËIDATx^í!@E9µá Ô"h.íÀq4`
8 ¡ùåy¨i`+ ùÙýÉÎ٬Se°ª_¥EÍÀ!iF]ם*j Q ¥ijCÿpØlÂc ú¶皣HOß×Ûu¬ÙÇßøùã0îw©ª¤qÄ ²f_C]$RQ|eğãø c¤e!SeÿåyÖÿÀI=0Mč8c
a
{S`ÿ¸ l?H n$М¬µ?fuQVqXÈ IEND®B`PNG
IHDR ì?O ÍIDATx^í=
P=x ã,<½¥±··ÕJmòE ! ä§ÙøÕI ÃìÛAߊVYþ=ACãmQ´m+J)£ÀoÐØxï/î{¹湌a(Oϓ·mÜé3_/ÀPUò¹DZ\Bº¦s¦ÏÝ:Æ([-i£['ÀËqDwµ¤iú£Ûç߁¦¡Ï-p]¶ þmQ$ü_
ÀûxGH(Hcg¼?ÓKq¢ IEND®B`PNG
IHDR óÿa sRGB ®Îé »IDAT8c` 0ôGFF¶022¬ þÿÿÿ÷=þÜý@|| *Y¤ZA| aN rx)°¢¢¢þãU
)ùí߿V¬XQ
s¦ü"\LLL %ä Ò˅Ӏe˖½R ØÄ@r¸ l6¹ÖrÅ.`
@÷3®¨Æj H3ºlb {©4ÿ/ð
ÁôÈéªþWª$Hmª<