// Copyright (c) 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. 'use strict'; /** * @fileoverview This extension manages communications between Chrome, * Google.com pages and the Chrome Hotword extension. * * This helper extension is required due to the depoyment plan for Chrome M34: * * - The hotword extension will be distributed as an externally loaded * component extension. * - Settings for enabling and disabling the hotword extension has moved to * Chrome settings. * - NewTab page is served via chrome://newtab/ * */ /** @constructor */ var OptInManager = function() {}; /** * @const {string} * @private */ OptInManager.HOTWORD_EXTENSION_ID_ = 'bepbmhgboaologfdajaanbcjmnhjmhfn'; /** * Test extension ID. * @const {string} * @private */ OptInManager.TEST_EXTENSION_ID_ = 'cpfhkdbjfdgdebcjlifoldbijinjfifp'; /** * Commands sent from the page to this content script. * @enum {string} */ OptInManager.CommandFromPage = { // User has explicitly clicked 'no'. CLICKED_NO_OPTIN: 'hcno', // User has opted in. CLICKED_OPTIN: 'hco', // Audio logging is opted in. AUDIO_LOGGING_ON: 'alon', // Audio logging is opted out. AUDIO_LOGGING_OFF: 'aloff', // User visited an eligible page. PAGE_WAKEUP: 'wu' }; /** * @param {Tab} tab Tab to inject. * @param {function(HotwordStatus)} sendResponse Callback function to respond * to sender. * @param {HotwordStatus} hotwordStatus Status of the hotword extension. * @private */ OptInManager.prototype.injectTab_ = function( tab, sendResponse, hotwordStatus) { var response = {'doNotShowOptinMessage': true}; if (!tab.incognito && hotwordStatus.available) { if (!hotwordStatus.enabledSet) response = hotwordStatus; else if (hotwordStatus.enabled) chrome.tabs.executeScript(tab.id, {'file': 'audio_client.js'}); } try { sendResponse(response); } catch (err) { // Suppress the exception thrown by sendResponse() when the page doesn't // specify a response callback in the call to chrome.runtime.sendMessage(). // Unfortunately, there doesn't appear to be a way to detect one-way // messages without explicitly saying in the message itself. This message // is defined as a constant in extensions/renderer/messaging_bindings.cc if (err.message == 'Attempting to use a disconnected port object') return; throw err; } }; /** * Handles messages from the helper content script. * @param {*} request Message from the sender. * @param {MessageSender} sender Information about the sender. * @param {function(HotwordStatus)} sendResponse Callback function to respond * to sender. * @return {boolean} Whether to maintain the port open to call sendResponse. * @private */ OptInManager.prototype.handleMessage_ = function( request, sender, sendResponse) { switch (request.type) { case OptInManager.CommandFromPage.PAGE_WAKEUP: if (((sender.tab && this.isEligibleUrl(sender.tab.url)) || sender.id == OptInManager.HOTWORD_EXTENSION_ID_ || sender.id == OptInManager.TEST_EXTENSION_ID_) && chrome.hotwordPrivate && chrome.hotwordPrivate.getStatus) { chrome.hotwordPrivate.getStatus( this.injectTab_.bind( this, request.tab || sender.tab || {incognito: true}, sendResponse)); return true; } break; case OptInManager.CommandFromPage.CLICKED_OPTIN: if (chrome.hotwordPrivate && chrome.hotwordPrivate.setEnabled && chrome.hotwordPrivate.getStatus) { chrome.hotwordPrivate.setEnabled(true); chrome.hotwordPrivate.getStatus( this.injectTab_.bind(this, sender.tab, sendResponse)); return true; } break; // User has explicitly clicked 'no thanks'. case OptInManager.CommandFromPage.CLICKED_NO_OPTIN: if (chrome.hotwordPrivate && chrome.hotwordPrivate.setEnabled) { chrome.hotwordPrivate.setEnabled(false); } break; // Information regarding the audio logging preference was sent. case OptInManager.CommandFromPage.AUDIO_LOGGING_ON: if (chrome.hotwordPrivate && chrome.hotwordPrivate.setAudioLoggingEnabled) { chrome.hotwordPrivate.setAudioLoggingEnabled(true); } break; case OptInManager.CommandFromPage.AUDIO_LOGGING_OFF: if (chrome.hotwordPrivate && chrome.hotwordPrivate.setAudioLoggingEnabled) { chrome.hotwordPrivate.setAudioLoggingEnabled(false); } break; default: break; } return false; }; /** * Helper function to test URLs as being valid for running the * hotwording extension. It's used by isEligibleUrl to make that * function clearer. * @param {string} url URL to check. * @param {string} base Base URL to compare against.. * @return {boolean} True if url is an eligible hotword URL. */ OptInManager.prototype.checkEligibleUrl = function(url, base) { if (!url) return false; if (url === base || url === base + '/' || url.indexOf(base + '/_/chrome/newtab?') === 0 || // Appcache NTP. url.indexOf(base + '/?') === 0 || url.indexOf(base + '/#') === 0 || url.indexOf(base + '/webhp') === 0 || url.indexOf(base + '/search') === 0) { return true; } return false; }; /** * Determines if a URL is eligible for hotwording. For now, the * valid pages are the Google HP and SERP (this will include the NTP). * @param {string} url URL to check. * @return {boolean} True if url is an eligible hotword URL. */ OptInManager.prototype.isEligibleUrl = function(url) { if (!url) return false; // More URLs will be added in the future so leaving this as an array. var baseUrls = [ 'chrome://newtab' ]; var baseGoogleUrls = [ 'https://www.google.', 'https://encrypted.google.' ]; var tlds = [ 'com', 'co.uk', 'de', 'fr', 'ru' ]; // Check URLs which do not have locale-based TLDs first. if (this.checkEligibleUrl(url, baseUrls[0])) return true; // Check URLs with each type of local-based TLD. for (var i = 0; i < baseGoogleUrls.length; i++) { for (var j = 0; j < tlds.length; j++) { var base = baseGoogleUrls[i] + tlds[j]; if (this.checkEligibleUrl(url, base)) return true; } } return false; }; /** * Initializes the extension. */ OptInManager.prototype.initialize = function() { // TODO(rlp): Possibly remove the next line. It's proably not used, but // leaving for now to be safe. We should remove it once all messsage // relaying is removed form the content scripts. chrome.runtime.onMessage.addListener(this.handleMessage_.bind(this)); chrome.runtime.onMessageExternal.addListener( this.handleMessage_.bind(this)); }; new OptInManager().initialize();