1W 1a11&1^91kS1\1j111H1> 1"1&1)111e:1?>1\1k1v1|1511Փ11611111111|1[1" 11i1#1K+121%51q:18G1G>H>|M>Q>?T>T>W>[[>K]>`>\e>j>An> o>mw>|>΀>>2>6><>>>>9>_p_ջ__:bbbbbRbRbbbbbb b-bbb{b9b bbbbbb b#b>$b%%hm'&hI2'h;5(h 6)h;*hA+hA,hB-h&C.hC/hD0hD1hE2hE3hF4hG5hwH6hjI7hL8h&M9hM:hMN;h"OhQ?hvR@hKSAh+UBh WChXDhYEhZFhg[Gh"HhIhJhwKhܐPhZQh’RhUSh~Th[Uh,VhݖWhXh?Yh˜Zh>[hљ\hK]h^h֛chdheh fh ghhhihnjh(khlhmhRnhŨoh8phqh`rh*shthuhrvh'whxhyhszh'{h^|hT~hKhZhعhݻhjBjjj;jj*jjjjjjjjjjj'j j!j"jL#j$j%jg&jU'j(j)j*j+j2,j-j.jx/j[0js1j2j3j4j85jX6jM7j 8j9je:j;jJj?j@jAjBjVCjDjEj^FjGjHj*IjuJjKj LjRMjNjOjUPjQjMRjSjTj@UjVj'WjXjYj7Zj[j\jP]j^j_jZ`jaj bjjcjdjJejfjVgjhjijyjjkjljEmjnjojCpjqjrjsjtjOujvj?wjxjyjzj{j|j}jJ~jjjjjdjj jjjjjXjj)jj jj,jjjjj+jjjTjj0jjjjjjjjCjjj j!j?#jH%j'j)j+j-j.j0j1j1jn2j2j'3j3j3jv4j 5jV5j5j 6jm6j6jE7j7j8jr8j8j+9j9j9jH:j:j:j3;j;j;jh<j<j?j/AjAjAjFBjCjEjGjVIjKjLjLjdMjMj0NjxNjNj(OjOjKPjPj[QQ

$i18n{message}

$i18n{message}

$i18n{message}

// 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 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.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 GetMostVisitedItemData(); 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.checkIsUserSignedIntoChromeAs = function(identity) { CheckIsUserSignedInToChromeAs(identity); }; this.checkIsUserSyncingHistory = function() { CheckIsUserSyncingHistory(); }; this.deleteMostVisitedItem = function(restrictedId) { DeleteMostVisitedItem(restrictedId); }; // This method is restricted to chrome-search://most-visited pages by // checking the invoking context's origin in searchbox_extension.cc. 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 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} */ 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(); }; /** * Updates the focus on blur. */ automationUtil.updateFocusedNodeOnBlur = function() { var focus = automationUtil.getFocus(); automationUtil.focusedNode = focus ? focus.root : null; }; 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); var isFocusEvent = false; if (eventParams.eventType == schema.EventType.focus) { isFocusEvent = true; } else if (eventParams.eventType == schema.EventType.blur) { // 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 node = privates(targetTree).impl.get(eventParams.targetID); if (node == node.root) automationUtil.updateFocusedNodeOnBlur(); } else if (eventParams.eventType == schema.EventType.mediaStartedPlaying || eventParams.eventType == schema.EventType.mediaStoppedPlaying) { // These events are global to the tree. eventParams.targetID = privates(targetTree).impl.id; } // 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.} 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.} 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'); }, getImageData: function(maxWidth, maxHeight) { this.performAction_('getImageData', { maxWidth: maxWidth, maxHeight: maxHeight }); }, makeVisible: function() { this.performAction_('makeVisible'); }, resumeMedia: function() { this.performAction_('resumeMedia'); }, 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'); }, startDuckingMedia: function() { this.performAction_('startDuckingMedia'); }, stopDuckingMedia: function() { this.performAction_('stopDuckingMedia'); }, suspendMedia: function() { this.performAction_('suspendMedia'); }, 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', 'imageDataUrl', '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.} * @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 chromeChannel() { return GetStringAttribute(this.treeID, this.id, 'chromeChannel'); }, 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', 'getImageData', 'makeVisible', 'matches', 'resumeMedia', 'setSelection', 'setSequentialFocusNavigationStartingPoint', 'showContextMenu', 'startDuckingMedia', 'stopDuckingMedia', 'suspendMedia', '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: [ 'chromeChannel', '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 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 , we define events with suffix // (subEventName) in each of the . 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 // .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 .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() { // Re-dispatch to subEvent's listeners. $Function.apply(this.dispatch, this, $Array.slice(arguments)); }.bind(this), {instanceId: webViewInstanceId}); } $Object.setPrototypeOf(ContextMenusOnClickedEvent.prototype, EventBindings.Event.prototype); // This event is exposed as .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(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); } }.bind(this), {instanceId: webViewInstanceId}); } $Object.setPrototypeOf(ContextMenusOnContextMenuEvent.prototype, EventBindings.Event.prototype); // ----------------------------------------------------------------------------- // WebViewContextMenusImpl object. // An instance of this class is exposed as .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() { 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() { 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_; }); }.bind(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_; }); }.bind(this); // Expose .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, options) { if (id in pendingRequests) { var callback = pendingRequests[id]; delete pendingRequests[id]; callback(result, options); } } 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; 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 getFileBindingsForApi = require('fileEntryBindingUtil').getFileBindingsForApi; var fileBindings = getFileBindingsForApi('logPrivate'); var bindFileEntryCallback = fileBindings.bindFileEntryCallback; binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; $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, world!'. So we wrap the // provided text in generated root level element. var root = domParser.parseFromString( '' + input + '', '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 = apiBridge || 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); }); }); if (!apiBridge) 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 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Ž,`N3QlyJ Ag uJ!C7'*膟.Ms *IENDB`PNG  IHDRJAuIDATx^Qj1@Q! ## K($y ,;ltYvBzO(#DQ6&}ɝE'̲ ,9VF|۲&ï&~! jY͑aRoFZ7'!fg7m>w%Za--4ȿޤI]IENDB`PNG  IHDR ?OsRGBwIDATH VmL[e~m ][_m nn#LDe1KtA.1:!:D1Ƹ(DbF6IƜT20-1BZg -6]'}s+d\Eڱ&y4|@Cm$e-x"(C] pUUYJ혚B0).`&w{"c d+ FX"p4!5^Fo&=ciH*,I  -҇nh:q3(uk7O7Yij\9m\Qƀqx̊NB qt)ǾsdWTՓ8D!v7tBS)|Z N,݋0m.Zb  *,x8qm l!@rW, Y8~yXS'5B/-bh4̑Iꓖ!IjrV,ZR/~K$bnG1ۡJN=&z h} Omkc!p"S R*^{́wÕIqgcc _#J5ƀ?7eDa ,ek4rh9Uba6C!0 l&"j2A(#I1 e > 10~xeL#)dS,Q5<[xRѳH5Mf%kߕ'yVnϡ_l7#a4|r1=5Sն~7+~lma+͍| ] Ǣ+Ozi#<ގ9ZaݢLx[aP4,"2.,J&?f .P$%0ɸc1邤6&&ۥ *QmZ RSkU~lR'_.g]IYzY[ݣ8+:ޒq1@f*K9mB79FB0P G=csQw=TcKl/yd,ZI&TkE IENDB`PNG  IHDR ?OsRGBIDATH VoU#m7v6nm%Ff` &^.Fȝ&zN aAV+ [x]k-x=̚uryy9|ؘrd=xR.Dޑbh4Oy+'?x$w`*!\E|#ܭ>ȥG:CfQÂX)T*-PRs꬀C }ãBP؀bITpzs\mZz|T%D$-;/Ǒ٫w1,^4=uH ܊׾ 3ci[<žVĈh&= l}ZɰZ)Gn-?M݈ 7"xۊ/>Jwqc7|7~;􈈑93A\u$ZoXO$7+=Ŗp\.E[}HmЫWA-^$A]P0Ljl:9'.ArPo8\4<@(ϡ$U btq4E6E<G9E[3 :w CC$VWW8,;Ύx<7`aad6ÿA\r>8H8 KSn4IJ1=5JtJ C!P@" HR T@P` j#)J$Vkyd|.J"zJ\\5tPh~)(Rɼ:*jXܺiT]Squ)ICZ7?9Coo/8ɀ8tuuazz "L gG0~"d$*׎qfϘ)ʽkI^0KɽSTPP%eMbM(Iғ닒QH OFu+=H M(/oBZYB'~!uH+ _B@SLҒ^O_@VA ~NM<3ҢO"?$4%(/nG*-':1_2ݷ%"d#c.i݅Gl/??hOIENDB`PNG  IHDR `vIDATW=@@P؝+HO7P DWxT֫ʝ 3'zu9@8YŁeqп "4+hxR* ^@U=94IENDB`PNG  IHDR ?OIDATxŖ+ACm۶m۶mֵm۶m۶)Fj/&c<"##і7rN551hO<"$M{Vl [At?7tA ='!>r/}eD~$y Wbrl[}([9(zQD {BHZ Kag,KsMe=9 3ρ&.v"+LJX( xہDiā)<3 e`M%lbAzR( ;ov|\AHu "q-\ҹ*0jZAQ'DYI(Ʌ>x=D5l \(R@Ā L}n=˨ q8H-JJ`mF2T#2 ;# Pː?\y$'Y [#;Y܏FKI 9E917;ޔW*>b5ujǯz}3Cx e8#`e`+ +-CW@wv2$=p:TP"jB+Ta3*;pd=D|)>eZ}Ea ,[g;&vݐmQ{mv?W,v{IENDB`PNG  IHDR ?OsRGBIDATH VMhAf46iCIBEJx(*)z(XA<(ARQ䱧H~C fQF-ka*@ rO'a,$øv CNM_@Rts0X#M?B^r9mȲ]֛6]T*R&pZV!raqqP`P8* ŢCLLL`mm nNSS(ߏl6A #L"ЎV:$) ? {QyIF8~GIENDB`PNG  IHDR cIDATx&Xvm۶mxmZ5F[_wr!;y8J@Jd^&hMD.!.'Aq]6ˊQ-(M8N;4J~_7hCêXZ3l'sʧfMaU:2(PԸͅנab(,~zhDN%"\۟ ]k ͻk~qjSmf8G;ɳc6TKW<d4 J4,>9_/&IENDB`PNG  IHDR ?OsRGB(IDATH V[HQmLﭦ^*̜DE䍲H!@4!"%ҢnBr9&I)ޯrs-'&=D"lll"`E2Z[[e<oj*Jf*zrR))tmղHGwuZuf>lpcrPT!3yfݨ( G.@R|rzWbS!WUԾ$72fv'ޕ}<:=J^ɩShQ>J/Jʑ`pXC;DH*Dl/Փ%N5Ԛ z6vF Q$lw+4w` n9R{6}mVsC;Vxҳя9ف&UI5 ~$0(@|l l96h`dۯkՑV'ʟI-^iMz (B?~R@;hlF;Azp3ձX,dvvu ̞\sF{_D1H!Q=\p!cF4J_w{pۘ ҝR*ṪRΎŶqC[[,v`>\cnfkqͬa yXHNCELx?r*4$IENDB`PNG  IHDR8$SnjIDATxoǫgƳ^?~~Ƞ$S}qfQ>"Ej@P 0c/KupUD~ Tl2nݱ1]+FOptD5~*F`v2e̍,7k}y˴lnYb{c7of]:3[t4K cYn5>Y׆iI+p,9#,oe#w<3sʪA@8xh͋'7kxkOyf,;禬;ًOLƺTn6Ӱ @=ЎtHr*@Qn>ROo܂#2xЌd#ggTc4'YlTէ0[cKO]]jnm,m\Y7ϣc׫9?|qT~Zfo뱪b1Ln)'IȽD^*O7/ Ẹ>1S;H.][m~ݽGM쓴mnĨf㵯nVןy~&=9y.lʌD^ľYL܄R_WZʱIy'kdrq˧f*8Qz Z1Yj"[%=`0'%qu%Vc:BTD 6|kʉLK()+uTO"%/;Q=4 zp#JG fr $(_o~T J'k,Mt m.e͇:IENDB`PNG  IHDR8$ܒ9IDATxOeIoL<\.㍚pm7 t 5Ȑ`0SS9lLB)@-F98 h )7Ha|7y|~M4W~8:<|\kCEMj*mա} tq6B:;TYTtx/~tt?-Er@s,m=/?M,h}0qR-m)@ZdI| IJMN rT¹B ߽^+?Ŧ/U(5-Qܽ+`pf;-)^~Umc ge|5S\7.?&.gȠyBmEb @f] yʱ}E5=]Q^>00`߅vl5[>4$݅ f_DžBaC}\.D--3A|K~D#w  F}O^N~/)df-5,;PN6 .AatzYii1:,/C!KKKaׯy$%۹GeSSd(.E!r2ߤcMP}qqagLKsFߺVOA~[ވQP@JRDi4ht{BjucCCuU%6C7qP<IENDB`PNG  IHDR8$"IDATx10 E9tJ0qnCnªHE*[DHzXSrfE3CWV(J0&1 YbZeyE[A8{o<9t¢",PZ`=KBU#OY!IENDB`PNG  IHDR8$ܒ)IDATx[]ek}r.s\@`[TPD.BZ*` jhTH@%4  h0[h+PЋmNg:g`k&Qd/edg̖s8 ^l'7!)6(x&1 Ɇ7&Ti*xU T)4 $,K} BZ3A@zb?®}޽"$1H(}0xNt#9*Eŷ>!ŲbN<Ҝ.LJq!l雿ʿ2:'(u+\vBX|*wP˔FiFL%<T"$XEAVJ qDq1weZkޠ'Is)%+$8u@FJ\ Eít?06G~fiFTG0@E SȲUO<˯78@ ΄=DiuII L 01[`(N n #-T8d!ym jء0`8vap+I@L&A1)::R`ѰVQ01(}1"@#(i$hm5Lps'@<ۑH !Ƿ=Ut*_"FT%'H2LAL#Ŏ⽲pAOJc 3iO ܓOjYN+ۄ]i޷-AoeOvƼųX~3.}%eQqe̻f־!F7måe 2(_Ȭz\uۆ6oolw"PV*K V{鞽tRj ֑A=y} GWəǝFot!yᙣ9d01Lvo̪[9!F~{y$gtLIMjJ0ݛG*aؿ׿B1:D3KD@%`F7_w7A*.3 ?o \[Ϻ-(r9sȋ[xmĀf'n.m~ g |ʧW#@#ϸ3$j,]t9(| M vY=zGH')}CjC4?r7P$ȄFy-tJ4\2}STQvdl]j Bku pj` ~lWY .82u v{e"*- 0ԓK MI@)'uJ!bGp1A5E%!JxP1DP5nbvusb0 :Ldrφ;%hA-g8f߼H"0u$aF2ASC0jѕ* a(b0 BDs?W'#!F q 'M'RZ b'B.Yv_uvSS;v ¤f3=xP-:΂7Ut/^܊ 1XXhFѿ&#"A.2q.OL1ԨR񂃡aB} SZae"W6exM kIpt.݈`zN tvgQbbUߺG (= pf`2NȋtBLd,J[cL&&%w:y) Efb#}r+b&)b}$ =Mkd#/eH_cʧʼnHǀ4PXd(jvTGt'J>p}.l_ =h= wHX^^ q]Nґ̟Rjګs{za$g rJpa4=]B6jέ ޒ]>2vs('$Ө!mVq. |򑄈]g{R8O;mU jBCHޡfriwVTot&)zy_\/UѧH F{\v_uXH*a8I=O-|j؁{`3'@ a -r47b$T.h+_Rp^S?6/)^0W9-9\l,%(}\ˉUri-;C2? Px&"WtSܤgS[%Ԧ&)vXR%bEvm3Ú+A@wsikYFH 57xrj¶bhK }rj7IUE\:?'Ț4㭱>θ8N-!2fNygmQ@/'V˅ _ҷ.͏ܐFXIfE- >]%Ljg*27 0XOksF^D#;-ԌXwQa?fbQR|*'X1wPϧP'&ĔEYFœ=8kvWW뭌iN0(橹.ook9\|*%\4Org2dpi|L9Swn4ÍI52TU:Գr%ӓs{(ttt5[b9j\ܲy+@x߉JP;ID{IENDB`PNG  IHDR8$SnIDATxsFNrGi~ `xL0aN$m)Ƃ:Pe;ٟ.T*P@A9 D!DDLx< rA>TZn*ƨ0h~䀓XՋBAQH3bIyQ: 4^ؐ$8Iy*IW"(Q@U .78! IENDB`PNG  IHDR8$ܒIDATx^kUe sa(V.Bk#PS?ԶjjcMՒjQZJ"̈#׹0wr[Nao:{Vf]QՋ"٫rs|˓Ds 9 .l6G<7C@[P,+=j|c„%;&DR|c@xD~q)<> .u\_1(( (pObɉ h"Z:Ak˗a|^ZlXB gP.kUW\XBv4kT5"R "C\ lymm% 6FIK0%QÔݜ;Dc?6qiexnNMehY~/dM{LQɆMCغwMB$3? U5bFHP b5E -wpFV[ 9y#_.W\/ IOy)Nͽ熅Sx\=oK9#,K1-w缹nf͕Qr%0j#2 FZ>fP3ժꁇ7ߪtf?MM޶n絭_3AtE~9ݸ^?m}*wܥvF,[{{']/tWޡ_3jjqT˗ifho' >K+R:LH!m飵{XN&3oKv.DgI%iɮ^}ܷfϢ-ͭ "˽^0;?AX=0X.d2NSH$ 0AxngU|k_ KZ` 7@``_G7 ?E1!j3/^L,_ 1xAcqtu"`c ֆ\:g.Ɋ7t]&M[R5G7m;NF8OD6.UeeVZ[D" iQ8``*Y5|?O[H~K\>i"# JNJcliIy1~E0L^AEI UduF67# y ~q! ؐCV1UC.KԷ⼱_񕕼`K_`&;!gGxA>TA t -N:'yq "aTE-- º].#V F4f wuA$A5C*ؐ Ç3""x 9V )F`11/cP a)0xM )ʞp\f5 ;f^^}GIPv`\ATT2 `U QP`8(O]$[n~U#`MD(Jax9-+'q|vyCXт)~) ^~!EhI'b2 G%Zkpkj֮XA{8q,HH8҂P-_Lgqf4`A=q'@ǟ//(IENDB`PNG  IHDR8$SnNIDATx^Og tZ֗i;]떥j&ۺeP"+.Vjŵ]ma[+ "nGo~۞/MrB넄׹uo AqPAqЭF<4=͵5H#@gK/olAԵS[6E Nn TpG|ew0Lb(A(K鵉uĴy0stҎ}vni 2rbOdtVR42R&@?V4q)x !@ʞGa"u|O bL"`}Sߠٙ_,91R$9kzCjRitpQ4?G/-- Jϑrjz $0oDO:WdǏwr+87@v=Pp4dg'x@٭fIG[J*Srd:6b:4G * .,tX^p@eI4h杯B͒dӞ;b3JJ>]< ^PՃJd}' K(1UX/VCL=Rb*SZU7@~U/69jy+Fl~Rhr݌h\H(.(biW4P11kF^pJJh<HāџF >z5p3b7~ej@6JƢ)հ_gT#MhQR蹲Լs=؀6Pp"W@a #ZRF8zk_QM60y wC~Adqvoj~D53>i4>QE F7?y8@f2KL)e8IDATx^g{Tuo3dRN$z/&wDzMH΢X@A{Yuuյ};3sN33;$'/%%%%%%%r?<{=P'bXq/ʀVi`]kiؠoke`6k曙H>e3HKEm$S_tnH#iVKӛҥf'|H4);$Yfny9!$.R>46R UF 1n,fDf$ؖZ ?(9֗auHMi>6~ð@4 ][0shXmvwT/b\H:"œuŗUTS[h-JoDm>L{+dM>RЂMTLDؿg*c>"S݈uSa0ß"l_/MSm96v0ѽ%VeSZ̦جj9ҭS [Ӥ3F>ᮿQTKoS`8೻k?B5?^dĈu0)yc# ½I7"Mhlm4-epmXk=<4)wJk'Qt[(0{mwSUCS<JtSY j*İ6Zoz,/FCLŴ;(wQ>MڟEč8:6 Dp<ςswRLiCqDQf)\KΏP|[ t /sE\xE7ـer vbAG)^ (b7 #fV[/\Hq̔zPA32S4 Mn:B!Ϩ>zbܙE.DIծbU z{ ;cqXV#SeyP/Q !·` "ҾA[T0,ז Dd#~YZӾR_K]ZBHr}j AzdzXTkK nkYc@LCPm!pRZD5S}`@5-> CMitB]S ׽(!.(ф}0dFPjњh$SQpϺ+4Lͦ5OUMn6&*#p-az"J "!ʩKnIgn.@D=$$&Z:^GڨiӍNtȺ(@GK-F#0`ݒ8|k=QU&M' ;4џ˳+/{z6knN^.9EGU ԂFxzY"H^Vj;YYay계 L(uU YMmV֧N~7 uD0jL: Zq|FM|O(%%%Uu}s# ԓo}:@_d=MrwxPvinKܬݦVsXYh4$zdiLyrTS%Hd":6bRPUhK__Iv9zGHcp b*f_apfQ 6k'N O@d0PKA(;?pgxx-)L@VV,%*\f5,Jmw&cbWp1u&猲ʲX.lY3'C#~Oo {!*5a?:^P'G5VG"Kk#-KrWsqishQ2: [<, ;c}w{¡.~XmYc'gK\jٛa'鲾}pv1HXl}+뮃6PB  g/T ]E)G53yj{ 1w[ ?> C܏d2@L5K1n/5#:Y}Cٲc]q:Y]ktĦ-`ŀdYIV}f}fm|@ >҃ۓ'Nw,] @ l _\M~W1B8Zu KU &Kz^i Nv1NSn)g; t5.27̩ܞx̛\nkًLbxrB4;f,e7C;7OJoƘ {N1PAP@g\ϣ8hČg}U2>\ZE-tȱFv.ɩ^%V' 8 Nm!fY&RgX/-MɁZ)]`%А; Ba1noYN@ (*,@@d,h-LF7>N̳C6 W(͔*rX!sGgXG?uF-N/]y0l:TqlO㉷Б"Hq ] 7U;yV]yXh~;pNfW\4M<Xq>45a)scBn)<"蚥(4PmR.WE]Trq?d=8u^`ҵGy\̈$̇S0㬄f<>^t;Z$1^} V<+7@g2#%\+! uq]vmܳv_$DUSTh'uHQ6<.^w{VX 'F>fa>?hӠ/ՔjrPjf|fOʗ%Y'B [TՇW˔iE~!} ˳2jٶoeIW}1I~# &/9=`lFa̳:f̳d`#?ڳIENDB`PNG  IHDRasRGBIDAT8R=KPM^:XA:A'tPMDupqrntsI~uUZ:sӼBzq LZ1 hSz!9G677Q %<JHH(  }w'tHC9#w%D8~ndyy駟޾u&G}/tm\>-6o޼y>YrرW^DGVO H___ .9j6M'}')rʭtxO>p8܍`mfȥhkkkk4==3H=%|rRBn^+fK~ɓ 귥Vnn䔴Y_x]U:<_ArAN/4c @ 7nؼvw}_!]bpO.\];?ŗs⦟bÑߖGҿW^^E''+(h"ܐ#r -puߑ_.--W7(q臖7~W^vR]ůwZJ~a.Rf{ِ#ϵSD, !FĊ)~䂜rDGN;__hnnn [OaU5T~tu{ Ҋ3/ K2/ C9PvH6Sh\1!6ĈX3bG9!7@_jkE1R7//ѝEU D"9ٹU5NCQ;ޣ̈́>jg73-c%@9(X1 D!VČؑrAN 9r~纳@m W?6&:hr}9ʆst^PX{t256~O calapr䊜;`@~?|h2#(Dj>U<O36*T>-4.p1G+rFTQ0”D4ߩ~2*GiuM猢˜C\3r dpJAQO!|n>U=U<9B={fS c A31K!К\3rԆF\-`bCƓፗfSM}s)J cf@!G6 ~QLex2_HVT ! oEȕ6p݌ SOύoXHn\JڭIu]^Mͯ6vO3c>#8Ďx+Bȕ`mh`ט^"?tGgSҙJWk ׊|Ʈ`Fu{@e 's;ƓZ93lݱ{<+_RccqC#ynmK]WNsٜWNzgkc*KtBj׷=LiZq@p1#v@!G䊜@=~i" oPr]G@e ^/!w9\ 9m)or R1,@NԆC7pO^1.ϵS3FDJ˥ɴJ4jW12Z)T-Vd#"6<aµz|Vէi,]}~`l9/^cw\ɨ4u[z bBlyp1ֺ & ]DeOS>B삳S7.BN0ͯj+~/ \{2@@&#@ U'x_zU~ 4 `;/g\"?f V ɠUw^':A|We pq 2B^`FL-RTxOX~ Bfd/mvOP>@@ ʷM甚"Sl b,A/Nu+ 4xJlӔF3Cs[vr[I?A 9fW&F *b_\\@M3tQVX<0'V5Z-cz;Űd y8ZaaA~ zmP75#*S&w)TBٵ.yj~}٣kRa,1tj=y6 `B<^аK@uNJ[ܺ_^HBLE`jA՗Uڌ,'-\4d>NKH3q}Efeh!Ik.\i1CM3.v l`vq7b+Ja9^e\HR~lMof 0yDBӨ`Zac"0Wa`0ATqY͑o^ם7gz&sWJLi~6uO7/Z:$~0_T333iA-x1[@Ԛ)u٧+nM)~kϦw,MB!(?F#JU fA6&g:?55%?f?I@h^h=0rrR~ `W\ 7bbB~ !p&pҀ(}drT3`0(?臿:MGP;@8!7HGOb+9\A8(VB?ѓJ8C>h[I/l g%0˜tDGJF/|d=t.4A7NM :5TA@ #XE[4V\O7' / ߗA @NIzt1qt1()1P<-RңOLM#N|મ#9<+6A  gK[*G7o>;ۇr? rzԂ* ^6V)MWJ|@8- !c_Ox46⠘hGK\ȷUx0Hn?xꩧׅ ہPb`råCdvCQ''_)Is] |,[v!!¡**5\jNCC^x{B/ /Z&;vB_@2}מ1~mkŅ\3rw ' B,STc"䊜;<O( B/c4ׅ\o#9##d  %$$P@@B.!lXIENDB`PNG  IHDR@@iq IDATx^[WW0AOqlm6k,ըQD@DxeG!(*GKjlͦ&qG{ϼ&3~x9{y~swK{ox4>xNބha}lZt7 n $ij>ÂpgpoA,8HgpЩ~gp?Hgp`AH(XLH@uhJ6E6OD#WPOLS:Dy#,đ2 22-/!a [\Jő~‹ӵɼb\>U),w)W #D},#t#pG qx|^NT+Ȅ}/@wMG^W _"ܳep o&@V5TNBSh`h0Z5Of-RԤ)6>znEX\M: Xv%o5:kG_7> )z(qJ AGLVw.Z?ô<}|>jGYWڧ!W=Yd bD/,Y+]q{vuh(X\`K,ťiQ;pDczgdbjvai6"ן=6BS"RNSc5w"O xjs]tY縧@a($HX@&ѐ=-gĶiCm^hY@(]fjPѮ!=? z )cI+Cu"_՝8'tbgSPcHBY]N47v&Y Їץ*/D]#]JڞR>ЇGq$?r*Ꜻ0Qq&n^ZyLe'q9|FqŶ{#'4jqAJX|!;#:F-4 9Z#05Qv^%EUeLBcjO\y0{GwHةDN~y6:SDv%6@uqB=JQfI2Uy '=L4{OVs5"˻> "*6ԄxPU)a%ň sx'͇4X6L EnULrRØljpx:[C"Bu2Gʬߛq^m/_\D~rfi}Li&ERErSs3 ޼ s/v_^Ez8_EM1B^\,0PY|'HĽ\W|SkiDBm}+U#8zVģݹņv4_l}2ΰ+1> Cתu|''؁LQ ҈י J}M#cpAbчT6,Χ|FK%6Au&Fl/KpTN"gn,,%jh<ސo|~i|(߮0֏]y¯ṔQ/ di/EUM7{Zaah\"Q󬊾э ѱ;Pj*\ΑC#$ D> H ehH"6>:s¿Euv2[5ɐ|uack<1Y[TGQ 2ES |@Fu|cйjOՉ*;+P+ҹ69Od_c\;Hybu ē~%%N->9n)zklm +wƨs-!q|15y֜V2 M+F: I?@^ @ B/lJ*eym«9n޶qT$55 (]V(:4%m v9zZz^#!;S"9Bu$H86bY5EЂfb:Z8g@{`3M3B!(d""FmP A#ZC 1|Zz&>k>" DD@$i5!_`@gOCR  AOk{|`/~_WfKS?v!۔&8o2^~7mQpWɄif>_[!GIENDB`PNG  IHDR&&4!%UIDATxֱ 0 A-i9+(AFH #Bsf!_ k?#e̘1cƌ3v &]*ڪ  ޮSIENDB`PNG  IHDR00 1 XIDATxر 0 A p.;BXx|EI{ܞk^ u VrG'#cH|ιIENDB`PNG  IHDR&N:IDAT(SI/CQ_ 6XX"$j5THMXAՐVU hQo,9ϳ9P DTF?e?1)CQW|~8NLI޼ːn!z89}3jؗAJvwG"lEpC.[fۊgk^K?!s K/O2OfqpEV74:D,GH}. vv!R!yDGDĊrC48H{Oh娛45b6$\n籰ےWȕȠ~  W6d;dZ FPT5ͅZXO8]gdlB4nߊ#w>Pgg;̠u[,*e;5 IENDB`PNG  IHDR>abKGD pHYs  IDATx 1tLdM˜hhGNjz@ @ @ @ @ @ @ (pIENDB`PNG  IHDR>abKGD pHYs  IDATx!Of "P(E4o4Q"DDA!Illwlwr|<+s66Yu&T?[:s27U=Z]vyU,Vugc3Xcu `W7ݶ83uUgmrnKS=R]չ:asXj- `RTGm|flV7v07uVglnK]ݯx 3XSuɓ:S8Utv:) `o5ǩsX8N@[ eu7?yKOL ?CخU=yN6x1dh^ s/ suVS=|"fh>58E 5c 1c1c1&v!:kIENDB`PNG  IHDR>abKGD pHYs  IDATxϋVU13q$DOq)jiĕF%pN"m72  s܁wӼs{}ys]@DDDDDDDDDDDDDDDDDDDDDDDDDDDCcB rF'@1p;ל 0x=9K{|^SA`3Y@Q]g6koם -`G(@ |謷8 <p[~Do)B O`r$$QݦѲ0P{}πҢ#@;O l0_e{G6'%Ԃ`#`L)@A`5~x-vrB??lz b> 0n `\o +>5ɆOX M l(yDj}*ЏOL+.O+;X@Om*fʮ}  l˜W|y P;}aH ?&UI/CEt"6W ?'ƐZ ? `}p짆9y p(8Թy p>8Ö OǾ~HG S )tp싛ؠ ~((~ pΟ`7`ֺbP`2U !-a=ໄ1G6T/MQ'ЉV&wXD~N}g\3|84 mRLA *Dd )^CyHT $}H}7& jjo#Gf(A xXṭwA ˚0 $~?+c:&Av>}iTNm)`r F$m_5 csFy{7C jw\ !p~ 0 \>r)?<.;|V9#`Y zIENDB`PNG  IHDR>abKGD pHYs  IDATxkA;hxI\@/%"'=zqM/ =( FA9 H_A !*>ݦ_aƞꞆ7KNIo8Sw$}!J =}\lLaj /T Kr]J2)~dd,7B%5!@%=|Vп靊75w :d13eJPgLHHF2@%~TD2I&A({R& _)g 1ktF͒ɧ~tI Ez$$_%Xˤ @^3WէUHn:9heKК&K~({0%h%H){~u$سJ1{&VW) A=ϐQWHS %`%Kxݰ=w(/L y,Yk^w3ٗl`9.ir$eɝEi#@`kVZ$p-=mF8%hQ K?' N )} Sgso┠iA8%< N y:ݞ"@S\>%$S+X6u!@)r{mP|^R#"R1]x<.C"$H%VBJ o {4 B / 57t @(A[B @ -&[u`S-I:pO7G )N&Y!rXxJ :H0$ȸ  X uKPMCel/OQ{a{ MJFSSS%%Cm uH8n٬x(☬Hw$3% p < `>lL Axk_I$8q,3mH$8go<-42Ij e&ÏDPW@["Y_0OPz)Ï@jVEd.XNoIENDB`PNG  IHDR00WbKGD pHYs  IDAThMN@P/`WڄB.z4K W~$J4v:4fl@A GZtND;JmP0_k2uVb;C^;N ͸138K  `f dFB T W+P4/ſ 4iU0W?@#%>:%gGUF+Pc^R :R r}:֦s*&r>LKWIENDB`PNG  IHDR00WbKGD pHYs  IDAThMN@P/`WڄB.z4K W~$J4v:4fl@A GZtND;JmP0_k2uVb;C^;N ͸138K  `f dFB T W+P4/ſ 4iU0W?@#%>:%gGUF+Pc^R :R r}:֦s*&r>LKWIENDB`PNG  IHDR>abKGD pHYs  TIDATxAjacH[Q  ݔхx;=CWP7A B*TL3˗w~?HI,dCIvt`d ?Hr _4Ij] Ē$ɠ&[3 Ʌ<,2;I |sVCWyjEvi X=F @ @ @6I&ydI&yf :M+yõ+\q A ?=R|G}+-b(#ن x; @ x#ApnA`EtkQ `n^p-o >Wwܞl1lg e$^M+#####@ @ lQ?-r6EG~N-65H2W˦ucK]zI$$X™$45JrҒ䰩]$y[>7s0Jr,[dIENDB`PNG  IHDR>abKGD pHYs  Ʒ.TUV } A+A+(bT͹dNN}0˞QIgf̊\nW#|"`c sQAxV45`)*=GrB"o"(=X)9q+9_aD@xl{G.Bh@ 8G #@p/ӆ@< 8G.p 8 =,|?o97TC֘J=j5.ЈBp0@@h h,a4}? `:TBP4%(`RO r]lp짋%v>&h@`2Z@} S _OahʬQ-x 9QB Z5`\ Rrɷod15M#\ pY8p=Z>,`6p&G #@p@ 8G #@p9 ؍&ज़L_M`qGw\/mSG"i@wE "z)(kӁub"p#p#p#8JY~"yE4NIENDB`PNG  IHDR>abKGD pHYs  IDATxܱ  +PPNweD$囶G @ M/`_ ~/`@ @ @Q @ @0 ʩ4y@7IENDB`PNG  IHDR>abKGD pHYs  IDATxڱ E̙ ,w6yUtXM~@ k @  ! @ { @ Kʩ4y{IENDB`PNG  IHDR>abKGD pHYs  IDATx[MQLJaf눔Kry3&OI!ID.o(4 e4"n 5%"&rd܆|+I^{~N^^{}DK$"A9F1+y ${%LjcW'g3 $@-e'Hڙ >Pde-5>De-M>De-m>e!          b#"y(i\l,J ~uldS,:dd $STKJIz @|VXշb DD)@*%\LIKz"@+fHIJ2[g$%{$Ґ୑ n6_2ݮ~)8-$%Y47NrI$;Cht x@\ !A>Gn$85W A5JZɔtN2lJ z +z10DCJ >$-}! +?1hRiz\@:-@*͒Y>E[rߊ-)R~VA/ W+Q,E0y$Uz|8Ph$fQFr=OǼU\R[*/pE2cZϣ( B )uk"E흊Z  >É7 Z PSQQg"D[9s%Oqf`V \Gu@H˕;LUԙir{7:ۢXhvԨB4a'urYDz8-Bݎ 3 W0Qc%VՊ=zGv{ZW5J X +T). 5n8jTf\5&opu<n\09uQcc11'_(GjG3/{$@{Y=`*/9Ɨ:HZ_V4u& lv.x׀=u*v}1k- t ^y6Gt=> 1hkW2K':IENDB`PNG  IHDR>abKGD pHYs  "IDATx[UUqJ%̐h%=DхJ({ +hXTv { K $#rtF{YAe>~>kk}ƌ9x[NZ_>wt88u+* A{YU ;`u/*jXYQv9P + հ2TN3#m PPPP # p /)]v8(v9gE8x"(@"*"\|70C`1(p# tZ' Puѱ5^R`T7x8vՂT8w WJAn/T,p5pP#p+pZ_*@M˾>L`4W_<lm+0*?|&8X< Й *@k\2DRJ+@kpV2WehmOxќsKH |/t462'.a@G>#ii8 q.R4`0sƴU{$= LӀ43?g:@nYx_ۀA:,ĸ"hnbgbL $tz H, Y N,:egffl -x>S ]8%ci~1+&v`87?Lk27WHVafb-(S/SwX]l^Fm͝?;S˲ XSsdOZ\`M([QCΔ#"""""""""""""""""""""""""""rt[!IENDB`PNG  IHDRRlsRGBIDATxkWqN |`\((_c("7nT\(.ܨTK҄&&6&mLh[tι,7wΙ33AS-@ @ @ @ @ @ @ @={vcf͚5/_/QgPQo߾}|̙+V^wc_ bgg甃}Yj@r~]OPs~=s:T1Avqľ@ uqľ@ə\)R\Kt||( _ |Zr 6۲eΝ;g/_ݻ?9+a<{  mݺuŋ ={q֭Yy|pƍhp is7ob6 f.\xU"T\O{^xý{\ĉvKlw @311$7B#Xnݻ&TVLu>xرcZBB*ollϿZN>.+p1~WGV$qUK s, @<Bӏ92{`hhG(Rtw N:K߈+U@XQDK /۱c[9*"@Wjܹ`\riuT#|1;rU ?xs΍m۶mv!lܸq>'8eY6m`I@k4V rNbPj] \ իWoeL30Um#W R?tD+C9KSĩP՞k{puN@'s@-q,u,mEt@"șɉ{Sat>%S Fvʺ^@ggT]ߊqѶβv>iO+@jZ/}>n/|8|~)}=1mK-@ @ @ @ @ @ @ `' @ OIENDB`PNG  IHDRRlsRGBxIDATxoMY;5TA{DR/^ăDܫLv:S(Z8kszznq{]?}Ъd@ @ @ @ @ @ @@ @ ̋/z?>~ş͛~LQUYw?vcccI&c٬4e.1h5eOuC4!ƖuNd0LcK:G)R9cG?  E @@o>A!ZgΜZz4вLOO߻}={5[(gϞcǎq͛-͛7{>uł ^|5kLmذ֭[:ps^ݸqޞXߚxpȑ @/[Ӯ]tuuaOn]bG! Yk׮:}P__ߓrȣ;w!e˖W^훚*?U=L @˗+TׯXh^UVM_rΝ;,Y3lM*0G,"3?u"ˢzM.@Lч18 Vݶm[sR`7+Q 19irwޱ:AX5 `[!-q.>|8kr1]g=Yo-l'j ~)ʉ E:WG%nܪn9١ss&A$JiN gS t @Q,ˉ5Rq4/_|r׮];qp[[T9%r$%SND۩T@^h%8Ρ=8&Y>@B*RU}C@-\$& 4u8r%u4{5a{"١V~X*3ݞ6ͩD+uJNuMHlUŲ>,{t @IHerc @cf9rq*5[C.]ꏬJ '0.i+W,n #7b =RJƹC' ƛ! P^.afTC>//^L.=XGZS4^5s %Mm합Y-J& "KOl/u=J]k$/M3uTi6Q1F z$Dv("VR#W* svIuRLm?>c(DZ--@J+6Ք>*u}|DErQ"@ ReX=k-@,{◿Y' JE=1q*ĘD'f\k ` @ @ @ @ @ @ @ A $qeIENDB`PNG  IHDRRl IDATx[LUWDjm Mhľ؉IMZjM&`L/0 bF5F4ɀ %BQ$V8ĈpQ(=(Q׬5|ss/k74V8o-a>` r$qqp8'99 . #^뢿@I7пD_CPIfh-4F?j8?qr9U>.}=9'S'qå^K"6hN ^c f4{=`kj4aӭaM5>SQ.?5N;b:v@G{dK @5-&@ZA{|מMc4R/ CsJ<=؋)&-OG՝ LDBkHO}e$AARK9V>1ϩ h[ųEJCZ⼎Ph 6ֹ9M4Q`7]0hh.} O M oOO~oi^+)+Ljruh h&f\&s;<`vmKh0[M[, lD\1AVă vsʇb+؃ M$$8w 6 t &IqKx9 Ż!@)$iCсd$:%ؔs;lLwBLHJH>Eg;Pd 9; @~^9(.P|Ea"Z&QTV O﫴,g&u [BfggJMc`$]"&''9svJ-:(Q {&K.e/_F"#!@;l۶=xRh , --?~uuu!d3) Q^ drSpVPP?~ɉHHFdѢEܹs@NfG"/(XlܸݹspӇE/`ܹuttr G(V,_]z9PBY'Inn.knnFMPoAbY,` C*(}]ݺu uE`WTTZ[[Qgg %.pHLVYYZ;Gc0Ơ8 `yfv=` #GN^f`' &+Wdׯ_Gcg p$v=z>XO`FLӧف`2"&b8= r `~v~ֆNxV@%K`8ߢaSJDLn݊QO D)HC]0#Pp~TL& .dgϞgC'![lذY"A!)9a &GPBxW 燤Xp !Q}0O< W! ߸-hA`߾}ɓ':E]_/^[;:C!=ίt!ɦM<  @nΟ?[ <Cˢ,!A߆jn޼2(>\1Vy F`+l߾555iW]?-[Ʈ\pb@D׳u@ F"=.?s 䃌Db(ޣ5|>PzMt@0Ms' ` Ve+rsmi tϗB >jUtl042[jFs ^ࣇ^\z8]kI׆CGuI>{N@ +JLR.r]{;}<^r}@W\4rЇM&`vޯsr޿[V-~IENDB`PNG  IHDR>asRGBbKGD pHYs B(x;IDATx횿oe?oA =[AQ;KD@j"!wx2eH4EH,'Z1%BHgeDl%CJIr>h.{;%85_ FN}ApsG[fs*]pgeKo6z!ӯc2 &xf8w1Ǹl6qMչyBD@˗zM/6Wz60 W$>Ƙ7&8=@M6+Z{7niAJT(~$@Iߑt q|I,=|߿+i,)7ƔJ>8./Up"0W|&@򭵟Y>8.O$>|p\>8.O˗xO, Ƙ'Nt"0%+ʘϻwe> Pް֞asRGBbKGD pHYs B(xwIDATxOheߣu1 z,^ b)g6!Bxo҃ C2B`A1D`ъmĒAL6;|?γλJhjt96IeZלs%jf_KZHt1I͎ û I/Kg\jC{Nqax%=弤86s M~hfK ~O_ҙ(o EӒ>랖Jx)N 4}8I+,R-+Af.3@Zh$&ז1/4 z}%̎KoA譃y=#[lQ\'#I4RF$[y<냒5$I+X}SQ6w^]ċ;eق Rߎqlԝsu433MeG)}kSZLO:77[@ewùy>Fn5\.>$F<55{}EJrszۓp:EQo/}@@g/sE1eE<6}XCsfKyZvcz>v&&&b^<ԭomm={}ƻ8{}Ex) ç:}JrBH>T*tQM:f R_3k׾۰9ݥRi9' XߎlwnЋ^~= <p>~#hs߷܇[ NK^zZomjyyР=IO'lIISo\JHd5˲g%No{,ˎ&Ire' \̎*ĺ/ZՖvUl a6yIwO9/i$[Z틁^twEIiPߪjt\cg[Zs]0$-i$&m,:~=IENDB`PNG  IHDR>asBIT|dIDATx=j[Q5lmL/@IpmHuZ7C`p Htȅm]'~qax_rm6-ˋyk$""32z˫_۞qL0N"bZ;<3#bsm*tzZe櫿93WJ&` ("zIDg^7~7i/ˋiW@k|3JN8J̻1*>*a.K0\L"X,n&ɇ׾ge^~DĻm0۟_DQDeOǶ7RfH)RP }LlV.@)DBJ@)JB;JP ЎP z"@)n @e6B*З]f%BJ@)DB@)D+о*P (>g,RfJJ /i@6IENDB`PNG  IHDR>asRGBNIDATx=nQa&qT eAqeXJD %{`'TQH ;羖R${9"B@@@@@@@@@@@@@@1M9?m?ݮλj4}[,ֻFo'=U~2~u&%]LB&:.L&!vC @g& Ρ I]Pم$`®s(d0a9tva2 J:0Lu%]LB&:.L&!vC @g&}.`:lK=N~ w4zZ=_>0 ~I%7Np}l6V ù]rd[7[gD |G |8,7w\-][D#p|Ev`(;GfxB-?\# p.?tW pa_ETjxc,:FP L-^WI=msUPI<@@@%~ntYCk?7yR?#(~zܗp_z4L8 zx4xR G- zxԷdl=8e}C@@@@@@@@@@@@@0x&"jcIENDB`PNG  IHDR>asRGBbKGD pHYs B(xIDATxKa;3IQ %B&bx,u$Td$%CtX:!( QYP3oN]vtGw_}>ww7/(ccc=7%]+餤 J}k5f;{cA0P.KJ:h$ɹ(~t0ƌrt"[-of짂tQ:Rf:EY2Ë@ @ @ @pNOOm[ߟ;@ !1F@ Gsgۭg9@ v3\g6~0GGo,3tΧrsw)] sI9ql(lAVcS&fggg=Io3^'I46fbے0ێ$ɝ(3 @*JX,HZ`YXEFwZ-)3)r\B@Č_6Rz#Q8cnY{}D|zc}5 >t,5& QaLiөoHHQAT]#_T! )j2Ȱim5JW?JuO v۶BB(ETsX4Ě%D_@qoFIENDB`PNG  IHDRr ߔIDATx^Ag\Q{7!RB(a(eha4UV颺(2dJB em>5^s2ͳsN@8@ b<#( )A/0u N 7Q }5_D F|N1%bgT0TP"9piJ& XPXHgHKEY`YPհX8kG22pғmiZzAbF+<0:{Aڱ"E=ʫ8A `'cFs;segiÁ-\5=٠bCL  e[` Pq {+ +c.hJIcGu K۾<~ :{}vg_viT? M|2N}i c9:wqΎnrqg;&wp @`Wbs"<^o2Ϋ^kk."Qm__^3d H8QE씝`*1w60nQ9\r$Bљ}DjZ}PDKJF^ޥ܈Bj -5ry(Ɲ]3}SeDsu{SG/]IENDB`PNG  IHDRnv#IDATx^UDZC0ku,%X%t 98!3IENDB`PNG  IHDRJ~s^IDATx^ӽKBQEXQD&H[CK4ED2Dc9CP$DmHPaAz@S(3c&K;N ЇeLxEPd7R䨑$A )NƸ&'Kq9,!3L3#!2H\ VG+Pe@dxYB l I(]v5~%}&Bo`yiN Z h4 eXS,fy#J[%ud; ąIn&W< x@b *xS|BQ YV$dxǍ6 Ff?)ڀhds(>IENDB`PNG  IHDRJ~s_IDATx^ӻKQ7LZŭ!Ab-jPCcSej(- J.'8{ پ%BG782M1 AUxHJCNq25@/a$AAP l/ifp`B}@A80=6 4D8A*bQ: jEILErX9`1C)>J3OXLVC29n@I4xK~z&kW< x@b |s(3,C̲H)ۏ3߮ q)A̩;vIQ`?iOi#IENDB`PNG  IHDR   IDATx^Ա ! Aߋ[ 9Ml2V24@Hi 4Hi 4@Hi @Hi 4@ 4@fTwn~2\O#[w~CNG2EA$G EUn+3WC1YBf.sȾRr,*c0*"V ЀO`ͧ?ladרk Gm(u (x М0ۭ~d[/)uAC睞\c\',kFC÷h 4S 4zC{Z3 7lDgAcq5h#Y]':^lT~J- QA r錆,<:L {H0Hx Dx9ш5d-eKLM%?(J^)N<[ =DEj׌_xp,'̚Pw;est4"\_pcRcpcEgPx pI4 8ϸȍ"G["1G>ytCZ UИшEmCo (a| "9S' '8ŷvXmChx-ߣ@F=4j@T?Nx, lC!Z701qjkFw(bP]hHw@~8&F(+kxк| 1U$>ٙ[Ѯ6LfߡS3O&" d)Z78SX+!G#F=g *>Q p,h/&U01h(8|`hN7ʱH @4 A@N5jj'X0CqW=\~5I ՠJ[W1!S atwRyWx^ jkZT7g*h|APdfX^ZцTgfRTrsY(i x 0Nc~ЅV,Ŷi6c0fXJ0`!" ;FJcq(w ,({/rf qcb!W@C텐p0_F#W:"1r@1!h8(YMGba`qCF@ "[IDATx^qT1@$9^s6mP0%!xr'1rq Hm7m HFG'G1ȱBOV%vsڅp /'/Ci2rcToBOnjj =[aÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذ9~ 7v_uF=;?ރrcݷ-ilpuޮ=ӫvsǧ.65 7b=xݼ׏mKATb6BsS2ZnsE9O1 IENDB`PNG  IHDRYGIDATx^1n0g˶Z:0LKIΨ/d ߎK hBh.MMP͊P={B}|a MW` FoԍYHOt7@U"K?\=@IENDB`PNG  IHDRi7@AIDATx^Oe}GXdNqc2bSslH\fVj5]cs+=@N6蜓ؾ /pAp B    ?CSE-Ugcqe /RWN}{ |c_Ceor>nl{rX C!?Rj WF:&(7OtYnf*7uZb c|{"@6lh:H盱|3U `[ G-|SI|;BL |~gKIQ ?_|*|UUW @>_|wxfXɊQ>TS$`@ݭ,fnS|<LEztM4apXgsk!l8AUQ,a3@LKk0B]_pȡ$uxN0'5[1jOtYL#wqG~ guڔvF搇#\Ye [g5%ï%SL }?leYT eX\YL3mϣ$8r#{c' (Fyyu 'P O@@<:C ]aִwquw<y_p_f9a!@!@@=ALZꨥ28sYLvyRf,[yxS=po_g I\!TyV\<:*(ı~ݤRE18 +[|dTvxT R42us)@ Fkht P@@ړ](eA1J|ai2F@=<c@sjc1JbL^gN t!ǣ>'C f/C!bf}"EoC%ni  ۰K@߮pQ xdAG *|l('P O@@<:yu 'P O@pIө~\iv K)37vcN@pgT.>k8SC`AvG;"QٱX8|D9n6U덕;8L;X:G:\x`U~LjaK68SDX=7@nz%a14YЄ\Ac&˗m8~Y3ku ;taLF&etA,8s#^bg~__2p2 naZb lq|1[w,ɾ!Rզ H⸷%2{q'I~-$3yͶ%ޝl{),dkxw\_Fa 8~\^!OLٲC._I۶JW @>_|*|EUUW /AC]4Z_ =6GD,"8-߸I$bpyil"[" xd*N[! Brb<}TF<^ D"_$UX'0@Z\*6 R7/\}ڏ +pjtE _C]ci\D<ݽ S1,Nww_5lo?2SL*xf9SɃ !ȏK IR$ |vKj\!@!@!_!n>IENDB`PNG  IHDR7IDATxڽ 0 De _PY JҤ+# {ȀCdməBIENDB`PNG  IHDR ';6,IDATxc`hB?\C0,!ԎiV'au< ! JIENDB`PNG  IHDR:NIDATxc@ %x@* ?-4 QO~W# T]H]ҕpOPk) v 9Q>9IENDB`PNG  IHDRAZRIDATx^a @ao] 'ZHmDTOHP%E#^а?c(AȊ߭ _镚po¢l'IENDB`PNG  IHDRAZIDATx^A@ E{&Cx <;& ^d@b7 FAȒ(lj%9&ZV ѮID.NGG@^lJu%9Q#Tr@T+^SBWV/Ƌ{oǙN8-IENDB`PNG  IHDRAZIDATx^}A @ EsA0=Wh]h5 ]KBi!۫I,ZYg޴pb`"t"Rt.4Z:#J@Zk aoR ӦG>pn PJ{F->/n\МNޚIENDB`PNG  IHDR7IDATx^ˊ@BtHK@ x]hw4]k1ϮU{0:9eq yȒ$܄K!`-V. 2 h9T+FH/lTR#{h$Je^%JgyHM|AIENDB`PNG  IHDR7IDATx^͡BAᯜE,TLٌ,> bC\}_9,~S3=}>tBN-p$#I8d 9VVH@#Q% aL5TW;FQUdÒBQp0f,R237?dw$uIENDB`PNG  IHDR7IDATx!nagF` m8C O 8W (!Z B!̾h-7һR{=5.fV iu"d lSuuh+l+RȀaJ2FPCʷ0:VԒ0+d9Kz 'ݔRdji¯ҏxY@#[HvaIENDB`PNG  IHDR$$sRGB IDATX ͘AkAߛ,d h/^z)V`O P=kAb$ҋ^b!`dSdf&cgwsHf2/3;)c*Pb9RraeG}uHW ߬Ov5\X^aT⧉=^xR{'|_G n ֽGe[ Y+- K 碦e=PTLćRӲPCĥ⯩f,Qn6_FCiSb5A$k)je@~+'T3|&K$딖 W `ZYgIENDB`PNG  IHDRUIDATxc  o>+W |Vj[A@*EMzb5復eJKA_S2Xᗣm 2ӦIJk H(>R]ˀV aOgz )>M Hg)-Aip 2$(- 'IENDB`PNG  IHDRUIDATxc  8+P>+- K 碦e=PTLćRӲPCĥ⯩f,Qn6_FCiSb5A$k)je@~+'T3|&K$딖 W `"{hIENDB`PNG  IHDRUIDATxc A&V ;@DCԶ ,?06T@ jP1 *KM^C Be /G%@|d! UMe@Q|@DòPR|6@.ϮSZ\!Ң+dHQ0 5@c͊IENDB`PNG  IHDRUIDATxc eV ;@DCԶ ,?06T@ jP1 *KM^C Be /G%@|d! UMe@Q|@DòPR|6@.ϮSZ\!Ң+dHQ0 P7ڃIENDB`PNG  IHDRUIDATxc .V ;@DCԶ ,?06T@ jP1 *KM^C Be /G%@|d! UMe@Q|@DòPR|6@.ϮSZ\!Ң+dHQ0 =tB;IENDB`PNG  IHDR'<IDATx^ԱKUaxa!C2)(hn 4U 5!Ys:8AC6TFf_K\9o:;>Pۨg_lౚ(VD̺Đ3 ӚU츰OS۔o.(25"ni,R=:Yӑco YN$יaG!L:c9~s{"Ӕ"4ԓc>!>j"WOZ2`A*f@4[J[ʰAKTVe#-aC[7^5,3XHKYC~Uv[YBxw iMWoRim֫%B[X۷+ZnSd~lW5rWٰEuf*\#Q U{麰_ˀE3~PyV'>-9+0! n',IENDB`PNG  IHDR'<IDATx^ԱKUQA> + AIAAscQ >( ɚpp>iM-ip"^νp eFpHjGOZ3!S[uQf)]R2m&--Zv[Kvb9Z-ƬHViϙwü$'IudU"IgsfB2oN@dMy$B_zrfU''Y͙C"\JIRT#Ȁ̂ ]𼜳$UAKL~k| *ٸu㭆+_z%v$_իem۬+!<ֻ V3'6&!.Y@]ҙb,;-?ovmq_șa ֙qG,aZ{uQ䧼`n 5 صI.ӄ6Qcpˈr@bp`IENDB`PNG  IHDR'<IDATx^MKTQ8ch9BL Z 2 @jad:ʅ}.EjS!A pN"98'4♃>x"шU3n.1pҔz+vBد.6雋Lń%Eˎ{c_ZزzuxmF-Kj˱P9I!Ig1+z grl\2gVr_dPrlEһ9ꕬXAx!r%8*#Ȑ~R13輧!ߢTb X z!mU|jl̚VwJZ)t8\W_廹ymm[TRhH,+[bT-9o;U@5y6䣁iEaMi+. vmK0"QO|[rNiXM VK?\IENDB`PNG  IHDRUIDATxc A&V ;@DCԶ ,?06T@ jP1 *KM^C Be /G%@|d! UMe@Q|@DòPR|6@.ϮSZ\!Ң+dHQ0 5@c͊IENDB`PNG  IHDRUIDATxc eV ;@DCԶ ,?06T@ jP1 *KM^C Be /G%@|d! UMe@Q|@DòPR|6@.ϮSZ\!Ң+dHQ0 P7ڃIENDB`PNG  IHDRUIDATxc .V ;@DCԶ ,?06T@ jP1 *KM^C Be /G%@|d! UMe@Q|@DòPR|6@.ϮSZ\!Ң+dHQ0 =tB;IENDB`PNG  IHDR-|1IDATx^ݝw׵1`v_%#6n\bm'93bEB`0vl0N|9+؎ IkK!@nIݭju̅kj< I=7ti._K)6Rhx͉OV13AN&߳ 0\=u:ōME`A,Dt Q)nRtPĸ' Vtu(=Q%]wSPhGM]PlXf;6ܚ|r`9 ^Bo5~ BWׄՇLyxaa_ żOLC_'qK"U|X^aT=PN0c))uy>v *a>Pr*!׵<`χ{ԋ5 c8J0F؜RZ]l^еę}mý~X"У:y TX:XQy v6 F 릆5QSyY6WްC< v]8;[ou~Y||ؼ|e3~XF3C&a._(u v=6;̕^YG>X13[(Pt`:Eq}ߙ 6a8 < LR;)nR$8E/̃W)Z)F` K9rvVY/ꀮMlow7|X`nyukڬXe>8kMo /KKO5_+^ć.gD|Ny(=aT-˝O>Jh9ޘ8M%5 8d ke \AQ[,{TÚGCW!1䜢}S(u/nCHszq B9ÿ7uZQWUv^V#' >C&)*ukV }#և>X+tXY+=#' 9xÇ._zo<~BC>=OZN݃>굊!+c0%7Vy/-zV pb;vUL1qՋͳ=lvb>c$&glO͇s `( rꙘh0Y Ii\ѾTF)7t5)V&Epr4kܲ,ueFdoL+FkWX3Jb["^EYaTn.ǒBXa bDzX`uVϡUYmn犼YGuQl~h!B2X#rhףF!W&CȻZBErc٧7_7|skC^rrNs8[QǗw:z9/جRs$XQVۃW,z zc!ӆkL|"se1ӑ!uyk,ydMe=;nQ:\'Y6PDʭݓY[e֣0˪K:\mXŌ[7stCկYi#96mL GvXふ"X}u1"W#[2+_ч|S%k'gW ֳek}^tʡK;BY}CWkR}k`vqhdOh_iUX]jokYW`=Ka 9 Qg>r˚Lv9똞v:]r={oB& cͺ7OF\貞Tay^́%^`Ymv8#?rKyKZu8qY\tc|41(^X6A4(2݋BXG>͚7b)43x.ސ=iHSnjvk9r\Vzw~lT>TrU/b5,rRWU>T9̺`m@9?׫\0g,{!Xt9ve9֥7v>CWEYߣygֶzԼ׾!+,=z֥ ~g9F٣cћ4 u٣tiMygV%XC' ,kW@4X;+CF#S3a||=lY:L ^G1\g*.з ȟA< )V<ۧ _kc^')_k6,~o_9Æ69{FtXLW> ubk=#\eeöWgrIڈb2H$ơ+=)֔"TqjyV3}u.+_}c[YAؗ^ xV1V},U뇺Vq]/vXbX=XR5rƑ7 K$e)`֔^7$!4Nboyz*B,{3_`%q,= }C=T~x?7Ɛ75WW| ް`kaL\=D^1}X憎"hX0jzAb,[tM0]{{^t _]1A]+-AWki.3Nz~b ^V]s* 7ƙ7[>ص: e?Waօb:˽aYcO9 {s,9\u 98K5&qq??ÝmvL&{sl6.= fP,+ņUVW,@Y6 X׫PLoa:ڄXV{CyI^8~vƪȡaΒC6:!jxZl9.x,>"Vl&C%mzT]{̽Q#9|Q7΃FサQ\*xM` :A➙6)2~ڰ/)78KXaV!7sq1^ԫːkmZ4dS ,j(ެUh]g~m#ւ/p_c\ _6uscڬ71ę+yƲz]<XLseCrAuU{%s*7u!B!x.3j|I `,]Ѕz0>=4k),xPp|qhQ|I kJהPՓ01onf|hp1mCDt!P1C̊yef/\:dхbH% s T[҇2Çrkao_Y`$篥,}xVrBx^NKü{u䚍$+=eVka ^Kn (ug.FFFr>`nllnLۧqq)wNB&Va#tm[Umc8g zSHW +,𗒬pй2q֣zXm`=FH]%`EY[5ԫ,ޣD&FoRlbD{c^!aS%j5u>nGNJz-~~q63dg+XZXr(=l5nO*,ǿѵWLW'u *6]> S\Q|4S{ {=zK]/i]iMu(UZWRMgOï?󠾾>_*oW[>sM7%Yt@ QEw5u8oH}\f!kL$/u .sYzW^tA#Xo(Fr,#X,fށt R^פKXM,u L]jFK7xzPqpຎk$͢;,'7y ܣ#{MU&q^դy9PqQ|8jԐãCWX e}u7{=E$6OZyYe{cIMMz[zu%qg ]G7DRWm۶ zzݟ#GM~5;;CWNs=VtzuQ4XZ]wP/:%uM#_;qJZ}FV?4W>"ȡ]LW%u1c!/mcKF~o~ST*/ S3)odŽA.\XB +V7X= ,Z`VGՍkPKz4իP˳.":X}7^yq]y)Z6NM(C a9u ]t{cA]6aQ1os:ۏL lgpGnaaA5-k8 JRF(q%s)i]3s߰gJeޘ5zk(%Z/io|t}jVT.pbXqPYfRjFuzC篨5隅7t[{:vJ]Ԟ͊pƼf:e׋8gMBִ)t[⬄ker390Od~~c'oMXHd,W<+2deNK:K>,0^Ҟ7t Sz/O9/Mz5Εw׫K/]LI+󎛖~i]^' _8]kI:%Qt5:iUxCխ^gao@7.ޡXaMXXGɳl:}X=Zuh4SuY>ꫯrSSS`vMB,mJ:ỹ;fԓ09TD,袐cO g)ouA7'H~nzǗV_E%X5jxX_@ЕY_ -9X6 L]oeAOb?eNЧ ?t.OReA 4:z稤.IuOQ& *t(sJwXuM/b?Y^ObDuQkZzIuI+9/<&M=V_)իWCv?͵cs: E0'))K/C!*IctpþT_QIVIM ,U`uXP:z{C^O0V01Vp`!oڰBX%=_/ #ҺzN92Wrotp׋Z&?bP@^`=iI q0]ҬY{Sݒ,kbۓ Kz.t^G4y ڙ.Muz$Yjgʰ^:؝9{n k͚Q|'77{zk\v1nմ>ܪ2oc z r{?y}wͽ;PW?WzJ /r$kdAWJw+],U9Z/UI(ra]Y…J=R%+a)o,Hkz-5ZԕXzMI^{cꥅzIxMxGJ-uӧ9ꇸvz/%>B@rSWjV^erN5Eta^&2 7܌ޘ!Z ].KTR8r(79V0{>%!k0}RcFDgK|N5VҺ_oҬkuT49\Z2֢-o# CM+ MbK՟eu"788_C=[fMnڵPW?:. d/hy^d:d֪kXjU+zDS<)Yu*۟:7:f]c[d~:X5̇:Cj UXk7%:"٣ Ϸzx}Iל"ڣϕ`aN} *kM4;`Y-`)b/)L^`]j<;k?N ٬c!Rڰ!:X5`u jm|fx+[/[tY dVԼL󌥸",zMjR p{kGs-Ap_#ƙYzo*AQ9%i=,g%'z@\9٭2_J̕ Zo9Ḱϕv9 x|q`Ix#f9X)y=Ԛ1ƈDyȌRNqLE"8O4`?n Ud*+Mˠ(aAoUG|X6b蚇._իEZЬO2Ɔy kJ%}] cAϺ2癮yO:3-(*fe"٣>ΜwU?Nxw43֖<ᆽށWOߕ~X7&RͶs%wl 3O ϗs͒gec"b*+M3y95iW0 Ck mYaa~ɓSGz~ t/H:P[YJ;~Ҟ x`e>հOYXˠ(a2@P+}F0ta-07t0pEf? A5ޛ'k=XY _`ZUu4d}DMDׁӼ T ]r]scyz@Χˍ7ȡgp;b*5>>\Hʚ9t>%e)x|EO#N]~'2V\xj,ϡ VP!R~ E*Nt/>N7ѧ&Փ]|Nh/uYwq`ei4_UmAl+sӴ9%UePBXjy9IumN^=V eT_3 UV<Ç, <Çf߬;ά5ՉB=D72`7c^nև%uUZG1ֳNk]o4|ao˿7Y>e A(}#z 9=0];8 i5K.= x cB>'.I;:ډXnXt Q.W,7 ' ]nrK.p O,KռzUb/vz]=X߇vm($"+^^W# +Xvzd!v{a)_]^tu1]lA0a|yE0]UJ˭>k.X\xc^.Urz,gIW'8K]4u}r~tZֵ7ttklV^ZGzZú@V?ֱvT\YH{J2tuyԵru}X`t㍀X\{>“7< gsrC,~q_@y*\ !bb @ دfQ {MA :#saV&0d&Q}X](ZO1ba5(Vj,&=QrJzce"eֹe^1̡҃zM\5lX=6|z̡UOnb9>7u^ 3%XrWXƎx k,jd,>ia`yu|zңA]jL Fq^e+4l_.f(E)c!cz-Dsb]~4״~ob`gHx܈vuIW7Xk٣蓭Ҵ `r62:mDgu9gFzbܠ+)+وҥU, RQTP=zS]Knf9tݟMMe(]m"]Ϋ :^Vη׹ SzSbhkoL!J/ra膃(6Ys\"Y`Wup<:O-LݖF |Zx:ŠB+bdc-/5huӍ|-ja,xчmzc5_OQeadsJϡ7bEL]\cذԋ(k)*+;<.RknCza}gPz} XeXJ1j CL'l+>rاGE+QW}Jy:x9( z)o9ſ/BÜXg9HX>ysM*qНG5|UĮNW%V .Y VPu UA4-jJ *߭7`yF]{፸z\߯Ğ!grQ#Sf|X)ORqjf bXGOFFQiv An?C/zc)ޘXX{LE jBjaҵσ8EX ˅{(.YSjZ9Ils t ×.]UV0 f( Ե=m9H g]Voƛ̕`po jê7\˻GVµ?]@W]f%+`- i,Nc٧[^B7|SXtMQT / 0xϻfőUϾ2+{J9Bk5 eMިC7Х$5('pR^Y7a(uO{NqJ@WHtFB1×ʒXKp2 3}?"w^NiM{~cu2o,U\>zu^Շ=)4kn뇊4 V蚀맚QlX562]Q&&d:)nl`x(oMvQ EH1(n[]ۡg :k|qrtTWĎq9zJ"m9=O9:t@OukE78uCͱ^F+qx3Cz? >ި~wc^s\ý >͑OZ?k}l^N$O܍Z^zc2t_rS_ Ey@%]G8د,u/[G5S | 6pZhͦQ +V9^['<h{"6z lV,ۢ<$(y8QB:{ +'>= 7=?e+Oʂܛw^JQ˾r_+V Ws r*t6>z 9\7uԍH)bqIENDB`PNG  IHDRUIDATxc  b0+W |VZX 1,T5x/^zԲtKXpR m 2ӦIJk HDC2 ?hzX*^>BF%uJK+DZt\ 0 F8 * IENDB`PNG  IHDRUIDATxc  a<+W |VZX 1,T5x/^zԲtKXpR m 2ӦIJk HDC2 ?hzX*^>BF%uJK+DZt\ 0 F"(WjkIENDB`PNG  IHDRUIDATxc +W |VZX 1,T5x/^zԲtKXpR m 2ӦIJk HDC2 ?hzX*^>BF%uJK+DZt\ 0 Fj.-IENDB`PNG  IHDR i,IDATx^͔JQG] &f b `.c"6,EA`u{?>w1-ZL eD2I ^_MxWV=> X^ZP!"U 3m}xY8=T5eW̃\|v5"{y@Ia$O.%R4%2Y-Y R!42 (5h0PYAGYbXY즛2![mF03 g_6zIENDB`PNG  IHDR iEIDATx^͔=K@ًY]!(ZVF m,-k[Vb!E]4qvBqCg3cPa0vWmٺT9Hyvrt 13=7Z3ecvAepp4ܣBCmQh@0F ġB*$7Aibka>XDŔ~"USDf_v\&RU3qm$=. ?V<q$`m.ΥRK#/[Y%&}w/oyU/AIENDB`PNG  IHDR =Z/IDATxc`d_1h1\-?irU6r8:WIENDB`PNG  IHDRnvZIDATx^-ȱ 0a* ;A\6Bp}bCV ыcf!6"ZP@vFJrJbm?TNwIENDB`PNG  IHDRaIDATxڥI @E=8xv؉qH5Q4H@+ WڭԢ#jwL г`xԆou뎰Sߛ2)^i.w\Pt6@ʲ`n|3'PϜY)U3bY6o";S3`,S+Hh"0O" AiG/pDR^g)@_/ 5>IENDB`PNG  IHDR7IDATx^M 0=H?$(nlBɴ)ex XlH0j]2[f.rG!p`I Ϛsbq G0LxBp%>qK#GMG2:# 5PCn,NM[gT"to/Z)VIENDB`PNG  IHDR7IDATx^A @ P4!)!d@+ZpTol8雼`>9Ɔbji$vuuvHvU|U,#uQ>(J:L$#AQ"z[9ZX4 ɸNCSo]KߟDeIENDB`PNG  IHDR7xIDATx^1B!\gςVHC6B&fʝ&_M`'c8ބHe*  ABZn$'M8*V!H%cF4T#rL]O.7_ւR!+IENDB`PNG  IHDR7xIDATx^1 Gϼ…[©+$R4,Zl|7 sNF¸ !8(H XtÖR%8#Kxn KULO|? )7vMSozIENDB`PNG  IHDR(-SPLTEa^_[XYOMNQOPifgA*tRNS@fIDATxc 0@3T P8MIENDB`PNG  IHDR7CIDATx^1 CQ%c_,y'QsW G^x O >S a&:&8oIENDB`PNG  IHDR7ZIDATx^1 1 RB Y_ R hB(Ph~BK-R 7n/F{9+L ;;L a^xIENDB`PNG  IHDR7AIDATx^1 CQ!c2J$_yQي:$V+<A L+2lJ !qIENDB`PNG  IHDRFrIDATxcOdX|alYKTXZϔpXgSlLXU179={Tv~S 4ǯHjN-jUXrۆL>C8LQIENDB`PNG  IHDRFIDATx^A 0FX{' +xHɔP:"`ԣ<2„|LGkphYR#FYQrX3ڈY$֐?l2AFs1zGn-nwF\'^J؂IENDB`PNG  IHDRFIDATx^1 @F"X'6E]C0"Xl,V^6_IA\͑ITC-b1 ţ>/{ oXy wVG?Mf.U"@];v='"0/؇b}+y ؒAjH"Lwy0*e$rV&pڢE3վwf3fe>g:"UIENDB`PNG  IHDRaIDATxڥK/QXhU/3jie7QEP L=.m%>'UF+79s{D$F49ͺ Ű7XZV5(o.'^h\D*O)4(D=u)ox?FKm`T^ 騻n*]*NG# #D`pB9Yqq5`~D9 L}œi==D [Icm w6F"JymN"Zm (kcfq 'ϼɑlIENDB`PNG  IHDRaIDATx^ON`ŋK6,{7$IJZz 4z ws@<!%q~Lńy}!"cۋSJK.q@-PWgXf)gf\6Kk׽녝x"xmYשlu[ݴ[DNXAaE:퐿,͘wx|i4>ukq(0^yTAF(xOJ ڶRL&phЪpL!D~}=؀{޴W;%뫲]ԼS\/889Di6c6xq/pF *E5sZҎf43GlBPgD,?[388An(K0E'~j*Ʋ{Fy] W~ꪯz#%.P'MC*\T)P3J f7ľZyRa ]-z`.G}e3 -`$@5¯M^gauX@,! {+;bfLOZ+vȕ:cc%uCmLHTOǢ'XrXsۨWIENDB`PNG  IHDR7IDATx^uQ;nA b{fi^%qqgX )q!DY=l GIN"P[ L3d7&fʌl1" "z;J. g3%m' %؂2G.w7Hf~F0ˣx/aY\%O ADbҭ_ ,7-9L7VI%u5j-7B64C"(" }.UC6:Oi 51м+ׄ= ?ͤwIENDB`PNG  IHDR76IDATxc DG X )}ՀWA:02+T=9\ DWFLHd<Cmr&?IENDB`PNG  IHDR ,:IDATx^ͱ 0_A**DFP0Q+ԸAP ''p ,xIENDB`PNG  IHDR ,:IDATx^ͱ 0_HA**DFP0Q+ԸAP ''p LIENDB`PNG  IHDR ,:IDATx^ͱ 0_6A**DFP0Q+ԸAP ''p /߸IENDB`PNG  IHDR00*l PLTEuuuuuuuuuЬtRNS&Kn]TIDATx^DZ  0<\y/: w~!{&fGjb쑚!{&fGjb쑚!{&f ~6 %IENDB`PNG  IHDR00 1 IDATx1 A`=_ b&~gfk@yg@y7@yg@yg@yg@yg@yg@y g@y9q@yg@yg@yg@yg@yg@yg@yg@yg@y7~ *u#}IENDB`PNG  IHDR00 1 IDATx10|%bPM]ٳ=C<3<3<3<3<3<3<2(8<3<<3<3<3<3<3<3<3<3<[Ahb IENDB`PNG  IHDR00 1 IDATx10|%bPM]ٳ=C<3<3<3<(8<3<3<3<3<<3<3<3<3<3<3<3<3<[V5BIENDB`PNG  IHDR00*l PLTEuuuuuuuuuЬtRNS\TIDATx^DZ  0<\y/: w~!{&fGjb쑚!{&fGjb쑚!{&f ~6 %IENDB`PNG  IHDR00*l PLTE8;tRNS&Kn]TIDATx^DZ  0<\y/: w~!{&fGjb쑚!{&fGjb쑚!{&f ~6 %IENDB`PNG  IHDR00 1 IDATx!0ȫ_﬿nF|a@y g@yg@yg@yg@yg@yg@yg@yg@yg3G!PPPPPPPPP Ȇ!ILIENDB`PNG  IHDR00 1 IDATx1uCc2OjWy%p^ Wy%p^ WJ`(QF %0J`(QF %0J`(QF %0J`(QF D?6pIENDB`PNG  IHDR00*l PLTE8;tRNS\TIDATx^DZ  0<\y/: w~!{&fGjb쑚!{&fGjb쑚!{&f ~6 %IENDB`PNG  IHDRKIDATx^ձPaρTj"łxI#/b0x%T]T4M=/KBmH*24"p~bo8ʩh@ v2P!7(+P@B@e @ Q P$8S \)p xD{dt)P@ p"R 0"tI.;b\ʹӁG@Y} 0ײ EOQ._Er7/666! [< ݂US.H(*r$(djBXtOOOobܔJ- Eű-J_D]_ω|)/y^](`g_%S|jNVX<Uws F;a}U;.T&9 IxE./ \Ymw1^D/O_މwW}\G{87|>h L|';5oᏐF-3ߜKGyqc3V!JgWdTgRhhhLt6}4ϩ$3=lSrNmNa{fq秖xkZG#?ese GfݑHĚCC^,ZZma``F/Lϊ44tY1'F688hZ1,^__pu^΅ZR(>M$}ccc{:]Y~q7)Sl&Z9IENDB`PNG  IHDRnMEIDATx^б@.`Xm4 gW{sX-x7  \  !"B0Ϲ5֜sČicv{DI"@JzZay"& X`9,p *y2E9 vFhC袡0om%{~rD #pm' VX7XӚ]/Fu) 5UgB:3X3_ (&tוI$"P 8Dg{Ф-P'a OGy?$3BGIENDB`PNG  IHDRnMIDATx^?ha.\Z5=*IEO Rp$Hs8A$A8itA)-5Rܾ^Ky>;;TB&Y,rXd0_$Οk0{kƗ+yښL'ۋAk}{x #nޫ j-/njbc~Me15BUmf2$3X䕗 ~,b1ǷzO&7Y%Ð 2P_/=}4Nn*Sn>%Rt3?tH= caeue5* d8ēn%"p7- JJd riSS7L..޶MB5l&T5AU TTAP#Txrv6pxZZAa tG@}~Y}g  !RxtpNML(,1(y;O$LL ęBp+6WxF%[2/%Q}>L"eD6 T껾Rm C6/%w,$~el >1N\2@|ss bs|&|ݾovn! C9rK6\U%;{V% 80QdH$-NGGrg}eHYk2g,H8IWWNxGGl5Wvtcl6Ks"jd2͋=X]]FD゛M]744h/~ԫYIENDB`PNG  IHDR;0DIDATxUp$ٱsTwp@efffz2^_3>W̌(pUVUH'+zIEdatgfeU5zCWccE4(84V^ߜ\o <̡$;Yt +YC QŋyI1H~lK[8&JH Ц+cZ98~ P8^I"6+V>/a@-qVEB߸7ܴI60|@3 pL}zMO,lD{G;rcҥSS&ΉeD !lW6Kz6ǫUTc φtO8ZXxB c!%|ok̊6uLY $7Յژ7CS6$\ViKsNtq"OMCG.2gIbE9 }1|g.Kv}(wR&nbHggιgq,ɱ1pRR5 b4cc)-̂T*qlxKl+u[p"虥[Ԧ$/^K̙| iQL?ĝ7r-qM/lWNO>򳀄ץm`pҠ9UU /]x'uwc\"fԯ/̳X!D~?sA+ŭc;lOBnсaN WXJ^GDH 2d|2IDATxc``ˀY}xIENDB`PNG  IHDR mf4IDATx^A P ?gQ\@zD$h`Kn>w(LʕQ #;8Ac '`NPiƚ-y ;zQXѳJ@)/JH8?BВ7L/ &IENDB`PNG  IHDR >IDATxc@ YNhIENDB`PNG  IHDR mf4IDATx^1 @F7nFP/ `!^8~m10003P‡] z> @t!/=q t_ 7GFzVٔOLl(DB#W,9l.#O*IENDB`PNG  IHDR /dIDATxc@ NK֟IENDB`PNG  IHDR LIDATx^+0E)B;<S ܣ{%Lv,Ë6fI5n]͍^xSV%E6ZIENDB`PNG  IHDR LIDATxc)BW PJ,sXāPa9#0 ' YM" r%D;IENDB`PNG  IHDR #2sRIDATx^͡0 &B/'@ɼݛ\kVCWN$O0-=URھcTn8mʨL̵Wv4tW|IENDB`PNG  IHDR;֕J{IDATxc?٘:@8 chfaa=y[nDžA ulvwgϞ yNIENDB`PNG  IHDR;֕JIDATxc?٘:y3 C=15y6ykljA uRODMlj u5cDj><ۿgJIENDB`PNG  IHDR;֕JxIDATxc?٘:y3 C=14/z޺; y:l6_0Xn@5jV]mm$TCsFl R-mPy~d AoIENDB`PNG  IHDR;֕JIDATxc?٘r@vK;3 ?G̤~y̔ljA u ȚYRO$ u ȚٴROY3V 4ԁԏtlZ)^FO7c@uCU%lUfW_ԡ3sKPy.5$`6<,m is!IENDB`PNG  IHDR;֕JIDATxc?٘:y3 C=15y6yqb"xսЬ&\=n+؝բw``8P9Hf `b! ÅlTE ^CIENDB`PNG  IHDR;֕JwIDATxc?٘:y3 C=14/zn}]I$Rg;U uyTzז9G@ pbgIENDB`PNG  IHDR2IDATx^; 0F?S;8/\+ NRJ;cso?p Ʌ,5:\ :ԴhʾD?hcLFZ+-&+>> :84T.#=90 Mj3pK4e{"C<#NZ'T8GTʄ+q=7nzIENDB`PNG  IHDRR;^jIDATx^ 0E+N]ڥ 3EAp\wGY!p )X#$Iʮ{q4Q߻93m GBr8iYP@EQ'd*TrUU4 ~Uo۶||7eP 03!!t D %KDɑxr8s u}GX.H1L\#H _tiYIENDB`PNG  IHDR2NIDATx10FWg!X@UũO4AGc|pˆ#~VIENDB`PNG  IHDRR;^jIDATxc?E:g@ U 7۳_QnMGϟ?` =*ߑlPʷ$3 5͋y 7$leѣWwAjj 6 b1"HU+IENDB`PNG  IHDRR;^jIDATx^K `-Dm4ʢvjdHoA0 \qDT+ +BaAt(t7 s]09X 0,BX4uX0$AzAiR ,HfJeYqcjfe PL@ePHpHpUK\Л_M9=^coc @Xc{>č7~{v u;mNJŶƕ&w(*%tsvk3MB>(p,"sT`n5tu-Wk1ZAǢfA *rk8IENDB`PNG  IHDR+('IDATxcXjfac30NO@`P,dž ~aw,C|A|w,ai;ӡtr,IbԱt迟?;x4ѽѶ]@;:UA=б@J1x0cC feIENDB`PNG  IHDRJ'IDATxcP99 ^$*/WIENDB`PNG  IHDRJ'IDATxcP99 ~d` IENDB`PNG  IHDRJ'IDATxc|P99HN]8!ŃGIENDB`PNG  IHDRJ'.IDATcooqƑf0`f`pWaIENDB`PNG  IHDRJ'3IDATxco?,O+x F)=n af`c`pU`IENDB`PNG  IHDRJ'.IDATcto/H9m,lg{ afd`qt8@zFIENDB`PNG  IHDRJ' IDATxcG C8dE +`A}IENDB`PNG  IHDRJ' IDATc  2!ÏARq9IENDB`PNG  IHDRJ'IDATxc|P99HN]8!ŃGIENDB`PNG  IHDRrH IDATcfIENDB`PNG  IHDRrH IDATcg@ BIENDB`PNG  IHDRrH IDATcx-iIENDB`PNG  IHDRW{IDATxc B,5 jIENDB`PNG  IHDRW{IDATc "<4SIENDB`PNG  IHDRW{IDATxc N?B+Tʹ_IENDB`PNG  IHDRآ, IDATc zݭjIENDB`PNG  IHDRآ, IDATcr IENDB`PNG  IHDRrH IDATcx-iIENDB`PNG  IHDR!D?jAIDATx^̱ @PFћ$L`VTkkGmMԊ|/E ~+IAIENDB`PNG  IHDR!D?jGIDATxڥ1@@@8J܏=R-72P+DŦ֐`| ֠$u4_#.IENDB`PNG  IHDR!D?j?IDATcX Rn df y9[&IENDB`PNG  IHDR!D?j\*SIENDB`PNG  IHDRJ'IDATxcπ'$οH8,.4IENDB`PNG  IHDRJ'IDATxcπ$οH?8,98>IENDB`PNG  IHDRJ'IDATxcπ'$έHg8$,IENDB`PNG  IHDRJ'(IDATcπ, FaM6l|0of6_z4IENDB`PNG  IHDRJ'-IDATcπ, Fau6 #\0e\`2=p.ZIENDB`PNG  IHDRJ'%IDATxcπFaM6(LMӽ h #"IENDB`PNG  IHDRJ'IDATxcπ'$οqƟp*,A7IENDB`PNG  IHDRJ'IDATxcπ$οq?p3*,?W&1IENDB`PNG  IHDRJ'IDATxcπ'$έHg8$,IENDB`PNG  IHDRH'IDATxc,ĿK.bX%Ǫ)iEIENDB`PNG  IHDRH%IDATxc,ĿK.2$رIjvIENDB`PNG  IHDRH&IDATxc|,Ľ{K.'\ uWIENDB`PNG  IHDRHHIDATxc?[W߿?c6o^$  E^o^p!l@,b oBd>W*IENDB`PNG  IHDRHUIDATxc?[W߿cl_>} $ p VԓE_o^p.@,@,`J d3F3ǵIENDB`PNG  IHDRHEIDATxc? OX¹ǛrªP%){OşBp (bM fK`](%a!IENDB`PNG  IHDRH)IDATxct % C$ dž@ +ŠhtWVIENDB`PNG  IHDRH)IDATxct % C$ dž@f`džvW.)IENDB`PNG  IHDRH&IDATxc|,Ľ{K.'\ uWIENDB`PNG  IHDRrH IDATcfIENDB`PNG  IHDRrH IDATcg@ BIENDB`PNG  IHDRrH IDATcx-iIENDB`PNG  IHDRIDATxc?[W߿?c(h˜IENDB`PNG  IHDRIDATc?[W߿c2IENDB`PNG  IHDRIDATxc?޽Ϟ=cxIENDB`PNG  IHDRآ, IDATc zݭjIENDB`PNG  IHDRآ, IDATcr IENDB`PNG  IHDRrH IDATcx-iIENDB`PNG  IHDR!MM]IDAT(cXh'[WY .xG,X/Ty wyw[=znq6 ,lpGh"6qKIENDB`PNG  IHDR!MMoIDATŏ @|HYM`(Z&Iej(r|Se%ϏZnfS`T ֈ6z}504K߅ֈ|73/IENDB`PNG  IHDR!MM^IDAT(cXhgϞ߻w]ݏ@ .xG,X,HONa^p}b.;Ā ':ck ;H ,Y;Lb9IENDB`PNG  IHDR!PHY IDATcظq'[WY}P#aBIENDB`PNG  IHDR!PHY$IDATcظq[WDr{MnyIENDB`PNG  IHDR!PHYIDATcظqgϞ߻w]ݏ@@vNtIENDB`PNG  IHDR!MMRIDATx @ Q S$%ie₈\RƁ@@ۍgf>z@03IDATc?6UY¾' Ē(M U F_; >IENDB`PNG  IHDRHAIDATxc?6ĸ%'# WY#]*!!ȈbCE(|IENDB`PNG  IHDRH4IDATxc?6@X¾' Ē( ?_TWg %/A#IENDB`PNG  IHDRH(IDATxc?V[®l߿10?/{lIENDB`PNG  IHDRH(IDATxc?V[®l߿10?/{^{ IENDB`PNG  IHDRH"IDATxc?6[®V[naxi5IENDB`PNG  IHDRԭ IDATxc```e`[IENDB`PNG  IHDR!}4IDATxc`d0f`e``2 A0#{s IENDB`PNG  IHDR!}4IDATxc`b0fd  @ s=lIENDB`PNG  IHDR?IDATxc`d IENDB`PNG  IHDR?IDATxc``eIENDB`PNG  IHDR iPIDATx^JQm p&7AaQA>,+!  . C%PR*L pf1a in0qYIX$!مӭ{ΖQ1Te#fa>ijU%na\"C ur DטgW]\ y}aˆ=;ңY/ \k6OPA$Gg̴Dgdsjmvȭp tĘ&Ґ>)\pl`5=AQ0r!ve{;bэ 'H#b]IENDB`PNG  IHDR@4=IDATx^Ԙ銝E=#nŨWaPaРH ޔup\ɥ)36ֺt^m@Ȩ$5w4wFƨH5QlkK| p0 f4F"w>nԜU 8V V68S Rw>ZػD_MCQ#7x2e$;po abl ۙ: P%7<,xm!^ӘkcS\ZVKPD55 )߾oUw69Z/• ;"QCGԭŽ ]81;C%b"->GA*fз|uf) x.\ݫLyBWhZ5#o~T%1'd f(R lp7(o|O *]F{4oRAl웇XnfjDz5PbVbqxR/kS+pl **l{Jtج{ܺ}ltn,ƚSUl!TI˜xݭ ŎȯRdAnW6wV+v1GQ.EX'G}:> )*5lQBnp"O[Nf)gVW49#QppX;u@둼tb7#SG1ضmo" |<0Zȋ^Hlͩ28~I^BU~ny؋lXʚRzJ'gՑ=L~gff_G3KbS'˷zCp`v;='q a[r @IǢg7b!1e$ D==$ޠ>}ąsoJboƷ<||ܓ~d7RTƱ \?M GbQÅ`P?-[a*!`E^#;F'nbͅ$ # ԥmp0:k 8vd)Ec^W>!& M?>̤0cE$Qmɓ't8|YlMUM쫻$fF/Ղ 6{`&k:Kn~rl_Ä)!U[q՗Wp3c(vq؍xŚN8H}T\Uy[Y!<{\ǪWΠQi7Y QY[\A.:g 9 P{*f`+- `!}X)3VeɎ=;6c:Z 6i"/V(h @!? xZ0G׮]S$P/k |H_(KBnP 8lI҇iYJIENDB`PNG  IHDR@_XPLTEݚݛޠ᜵ߝߞࡺ♳ݟ᜵ޝߜޡᠺᚲݛޞߚޝߛݟᚴݚޟޞࡹ᜶ޢ⢼⥿夽㤾䥿䦿奾䢻㦿䤽䢼㣼㣽㡻⤾)CIDATx^Ir1SR9N,a[j] 7lm>W.Z4ǽ%s RU4wFhH/_YӺQČ]l&†qa3Af3;c.D@ƘA!0B@ Jiu6W?vٯg0(*| ك/b@ Q"_vKekץ7jmsV滽lFZ8Ku@PP LU#QpEInzu= T&*^I{Z.۷X;ck><7~:-3cB&hAvZ`|ZjtB$ HIoϙhIENDB`PNG  IHDR.ubIDATxc-D%@I+0`-jxJ %Dڡa `%t5S3#x[. XcZcsGIENDB`PNG  IHDR.ujIDATxc={v#-D+W{Y$p4$//)1,K@b0HаW0QtyYBR<-@FK,PcZc]d(`IENDB`PNG  IHDR >3IDATxc103LB,.v6(dhahfhCFdCC-KaFnIENDB`PNG  IHDR  IDATxcπb)Ԇ)@5 z 1BEIENDB`PNG  IHDR >1IDATxc1(1h!< 9| P \f !Kmt,IENDB`PNG  IHDR >1IDATxchá1030p@!'4 \ H@.3%SlIENDB`PNG  IHDR  IDATxcπb ONVo]Z~IENDB`PNG  IHDR >4IDATxcp123@! P,LAH H\ B7IENDB`PNG  IHDR >6IDATxcπ H2@Y1edK1|gd0sY@\`bpg8XE{=IENDB`PNG  IHDR  IDATxcπb2&ONR ,~IENDB`PNG  IHDRy!\IDAT(?KaߝQ[EIN*up(Q>CECN~h>E[KkSpc᠂dyo?:̑m^ֺW^$/RD)aOl0eDzDPuR2T* } v#v} dG\ 0qod\L07Ne)y YսΝs{- 9cp+VxM1Azr>?}|<ۓsJq4,d>{TȐBCaBE#M4[lE$6q4T(,6XD(P K{qIENDB`PNG  IHDRy!IDATx^1 + q&Cv8xDU0AߡU c_QR >5N4L:;w%?w2_Qa0K2*-t>5aSa`r]KY=e~'ҝVAЅ5v+iSz?WaqVCd6$saCsIIENDB`PNG  IHDR(RIDATxc`H/NdHi@ QM 31!M@~ڞtMz)N2ire|9@&soL<ܾDrNr:G% l! R1c}u}Zi쒧=>4DS:1 <NˍjޣxPjy< 0i3Ęc/xP u4mv-\ }@IENDB`PNG  IHDR25IDATx^NQ&@60h4!D# Fc@BMx:g3ilf2_]?/ *j!5TPg'|<{=(o (K( %U­fDKPPXdgj#.<; #[ٸTdґWґO4TSEzNzT W&]Ä7ґo] YzHWNT(Ymߍ;js0ݭx0 #rsvs rT993Z8c (Y{J%4;IENDB`PNG  IHDR2IDATx^n0OPœYy?$v#oB"Rcǧ7v<*7O^ #A*$"|0ȳP3 `˳*ņMbLךW:ԢL0<)\,JU[) snm#U!qBjL%?%_m#c )]OE\%/ل/ezS ݘ_2f/*qQQ u>UG>Fx+Fߕ१9 S|IENDB`PNG  IHDR2CIDATx^JaqbiQBjaK٘K7^Z!noA\^0pz%m{ag` q89driwei/WD'n5n(D Ռ}:@+$UHGI&>viaw4x' |BwRGR b(FAIj 5)S7+^mTeu7C^ҕr`<-'e&aJϭRmmGzLZlu4܀sGl64q2p-i ։-$ML1H|')#IENDB`PNG  IHDR2XIDATx^j`ǿH!8XNF .EPl..>/DBDцs.?p ,,"R,|$0Y.moar8XXh‡_@=0+E z>Z(-J\t1ͅȭa W0H 2IG U>fB&&%0݆\ NOD o|x"W3COtM /фjKR6"6ڎra XÃju- XCݚw5qxx,qRHrQ A!QN_7>LeIENDB`PNG  IHDR2IDATxڕ= gͰ3hr)u"ڡP*1F"\DHL(B O[&2C)@PrУГCUZ2/r4Z6?LY@G`6ԂDYBr^7ZvvȖ!+k Z9-lM: y1Uݯjǂ\+o-c#]A]IENDB`PNG  IHDRR;^jwIDATx^JQM wnk,Jm 6VR,\AB0@Al& ps)q}—_ {P*mŠhuK2 8 &VE   ;0?%!3pΥyLNcxT[hw1`Re{Q N] *:^['A`)lnZAWK[%l=U6r+c'sxׯ",:J@qC0w!q ,:JВ M 'WBI ь3Y$q6SKo>MsNS%A(֜_yA:IENDB`PNG  IHDRR;^jIDATxڭT=KBQHڂƢ$2A('!s!Z 4EZ*$lc!(uW{zCؔxxs=^{B \;APBG=ZfV$nj4Oj(af 4x~ö]U4Mh4afT]Wu"t:-n74遗2{\0T霼l6+~BPF"@|>G27\0_P%.r8fZ>хdCMFZ k/2\@Obb5.*܉GxQB 9fȳoo+92R::V GFL#hP!v 3h]?3A|:HIENDB`PNG  IHDRR;^j IDATx^RKkQUbnB.big !!$ PmARiTD d6"IA?Ӗ׌-d\Ւ8u1Oū+S+fuQ_Sh ;>4bO6kspi<?hǓZauq@C^dDLJF_ &y;9y _KןU>"l&, x\(QAuMx0 Q/e {p88K&PHڎ(c!!ueh% Ncq39B{,|T T*-Xگy~eqe! ,bb[8f^Wۂo6z-W?l6 (AFwWf߹DA :Jh4Y)cA NP p8Lh$Sڶw BE"3]T!KKGrrj,٬~dy <\~ժnKLF3x {z V쟞]~pȶm z=r]@c&G}F0oK%8Whp2bgL?&>lP\1wse8Ɨ/vgoڊ?<'\s9ǜeN2orA[ypIENDB`PNG  IHDRR;^jIDATxڥMKQ 5l .IrNA~@A/ZXE-DA I j6ΔLW{`0 ywi"4P5Jw][Jm]_6fuh5kpxK/T"D~5zt&oM@^Fw^2aJu<o=yI3AXPzDۺ8Bc}pЀTNmKp13؞ B,Zw?8ʔVBїt] _f[|8HJ+8|Cq@3/6Q%Ď=y65&*//eZ+,%'ѧRH^zF3xr^05Wʉ \q,pq Y'/1cgb;qSSyLscX-L.kklpIENDB`PNG  IHDRR;^jIDATxڭ.Q-܀, S[H Q;ZaCB"bA#J.Hv3m7HhԜ{424ɜ3s i`|L%.Yhޯ;i~'*E@q9֞-0{$2iFT `\E6N(dlvȞ^LU^qx9`8=u4V,udtM|4&S:Xt+g?\Iq9``θ=L*]ί)HT,ޑ%n }ѹF~vt;BUNn$\UU`9,ȔD<\ۦc`X')sЦ${J~ΐ~j~gضĴ0L;4BJKIENDB`PNG  IHDR2 IDATx^=k@ A"9UZAeů&._A?E4;;6"nB/tL٢9u{.tiIENDB`PNG  IHDR:=5IDATc=s#Q}b20ıE}3ɰoe ݍA5IENDB`PNG  IHDRIDATxc P6SNIENDB`PNG  IHDRVIDATx^e r'vqb*܋`B Q/iA1ς& W$ E17f-A/X;\o$IENDB`PNG  IHDRZIDATx^]A 1IJ#=~B_=t"l,x !̤OGAH7iA@"@ EzO[ {ek"t ?nj;TcyIENDB`PNG  IHDRѝIDATxc X6>IENDB`PNG  IHDR:=,IDATxc@AA?cʪw߱uqܩrIENDB`PNG  IHDR:=,IDATcπ |B;p:RL?˟~Fq# n>MIENDB`PNG  IHDRѝIDATxc`g`0f`d~~IENDB`PNG  IHDR:=3IDATc`ɠCp`d``5 .J,IENDB`PNG  IHDR:=3IDATc`B N f QU{ppۿŎk-,v["54_0?@T8')L1К$[Dɰ0/Ŕo5`g n p)2ek SS=uc![{U^/V0mȱ/U7KGq2-u^.!xDGh@;Q93 Auʢ9F1'flV)imJ$F3]6Ws;CBIwA6R7So"~$9A:ECsH㛄 ,W:%ǍW,'&"L.oQ)GDlU,BDb,9eTc;SimMHYAoT|"hL[dӴ)`1M1N&řH͹Rď'"RɊei[IENDB`PNG  IHDR9dR8|IDATx^kj1 '"NMhO[%zƚ!3)vlkߎ@zDE| u Uϭ Uu\ l;N% ,}78cv«[yS¡!s&T^kN~vƖ.iib^h 'xN_}9%3)ϭ8JBTxg`WJ -)#)VV=0s;lm|l%g7\ PzL@4Lԁ]m=Α x4>/F+n3"OuI+|RL1y%o.2MJu2;_!7gIENDB`PNG  IHDR9dR8IDATx^mN@QC4+t soLN)w̴Ng d+c}$")6h.Lwq3N3nzlFM768d\mKIgWN.25.=k *;sm,BEkqV⾓sI]w:־?GC+{ |bp_ta|#/ NƤb lR=0f~6$Pp5o<}o ڬGR[p`w42M{\۹.6's]LIENDB`PNG  IHDR9%>IDATx^ˊ1D?4fADDDf,@򞍗#ia٥S9G=Xe:Vfnd #Hnm)_OW[Od|s*5tlK\*IwQR 5$a4VyiVUW1FЗۂ):#`c(ߵq#\WZ;OTỗV O#/fWk؆''/^7y`"Ho*ۊ;@X]I0j PpӔEOsۂ+|X)b fUz#M3ΨYE?vB iXrIENDB`PNG  IHDR FFIDATc`Al cǬmIENDB`PNG  IHDR JΛJIDATx^1 0k-":z D,8f ׺t>&"h[:2dABH=RIENDB`PNG  IHDR JΛJIDATQ @@m㸃S8(mXP(TlTs:]tnDs;#"Ow0JIENDB`PNG  IHDR LIDATxc6W8XLGIENDB`PNG  IHDR LIDATxc@ l LljҰIENDB`PNG  IHDR YOoIDATxc@ T9-Z)IENDB`PNG  IHDR JΛ0IDATxc 0Bi&yad)%M6T&;/T!&)UL!te^nIENDB`PNG  IHDR JΛ0IDATxc X "&F4g`DiE't!Vm\\IENDB`PNG  IHDR SIDATx^mA @ FgEtkԁxzqG1ZFIENDB`