PageRenderTime 105ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/main.js

https://bitbucket.org/EMPulseGaming/firefox-zeroclickinfo
JavaScript | 611 lines | 471 code | 93 blank | 47 comment | 101 complexity | ca1a5fe18cf7ce6e3748b7c4231d90f8 MD5 | raw file
  1. /*
  2. * Copyright (C) 2012 DuckDuckGo, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. // Import the APIs we need.
  17. var contextMenu = require("context-menu");
  18. var xhr = require("xhr");
  19. var data = require("self").data;
  20. var pageMod = require("page-mod");
  21. var Request = require("request").Request;
  22. var ss = require("simple-storage");
  23. var contentPrefService = require("preferences-service");
  24. var prefSet = require("simple-prefs");
  25. const {Cc,Cu,Ci,Cm} = require("chrome");
  26. var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  27. var {Services} = Cu.import("resource://gre/modules/Services.jsm");
  28. var {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm");
  29. const HTTP_OK = 200;
  30. const HTTP_INTERNAL_SERVER_ERROR = 500;
  31. const HTTP_BAD_GATEWAY = 502;
  32. const HTTP_SERVICE_UNAVAILABLE = 503;
  33. const DDG_URL = "https://duckduckgo.com/?q=";
  34. let browserSearchService = Cc["@mozilla.org/browser/search-service;1"]
  35. .getService(Ci.nsIBrowserSearchService);
  36. var pageModGoogle;
  37. var pageModBing;
  38. var toolbarButton;
  39. var popupPanel = require("panel").Panel({
  40. contentURL: data.url("html/popup.html"),
  41. contentScriptFile: data.url("js/popup.js"),
  42. contentScriptWhen: "ready",
  43. height: 60,
  44. width: 530
  45. });
  46. // meaning on defaultly
  47. if (ss.storage.meanings === undefined) {
  48. ss.storage.meanings = true;
  49. }
  50. // backward checking for DuckDuckGo set as the default browser
  51. if (contentPrefService.get('keyword.URL') === DDG_URL) {
  52. prefSet.prefs['ddg_default'] = true;
  53. }
  54. popupPanel.on('show', function() {
  55. popupPanel.port.emit('opened',
  56. [ss.storage.maximized,
  57. prefSet.prefs['ddg_default'],
  58. ss.storage.ducky,
  59. ss.storage.meanings,
  60. prefSet.prefs['zeroclick']]);
  61. });
  62. var tabs = require("tabs");
  63. popupPanel.port.on('open-ddg', function(url){
  64. if (prefSet.prefs['dev']) console.log(url);
  65. if (tabs.activeTab.url == 'about:blank') {
  66. tabs.activeTab.url = url;
  67. } else {
  68. tabs.open({
  69. url: url
  70. });
  71. }
  72. popupPanel.hide();
  73. });
  74. popupPanel.port.on('swap-default', function(def){
  75. //console.log(def);
  76. if (def == true){
  77. if (browserSearchService.getEngineByName('DuckDuckGo') === null) {
  78. browserSearchService.addEngine(data.url('search.xml'), 1, data.url('img/icon_16.png'), false);
  79. // it's 1 (because of magic and you need to read the Firefox code
  80. // to understand it)
  81. }
  82. contentPrefService.set("keyword.URL", DDG_URL);
  83. } else {
  84. contentPrefService.set("keyword.URL", "");
  85. }
  86. prefSet.prefs['ddg_default'] = def;
  87. });
  88. popupPanel.port.on('ducky-swap', function(def){
  89. ss.storage.ducky = def;
  90. });
  91. popupPanel.port.on('meanings-swap', function(def){
  92. ss.storage.meanings = def;
  93. });
  94. popupPanel.port.on('zeroclick-swap', function(def){
  95. prefSet.prefs['zeroclick'] = def;
  96. });
  97. popupPanel.port.on('meanings-swap', function(def){
  98. ss.storage.meanings = def;
  99. });
  100. popupPanel.port.on('advanced-maximize', function(){
  101. ss.storage.maximized = true;
  102. popupPanel.resize(530, 220);
  103. });
  104. popupPanel.port.on('advanced-minimize', function(){
  105. ss.storage.maximized = false;
  106. popupPanel.resize(530, 60);
  107. });
  108. function loadPageMod() {
  109. var pageMod = require("page-mod");
  110. pageModGoogle = pageMod.PageMod({
  111. include: /^https?:\/\/(www|encrypted)\.google\..*\/.*$/,
  112. contentScriptWhen: 'ready',
  113. contentStyleFile: data.url("css/google.css"),
  114. contentScriptFile: [
  115. data.url("js/jquery.js"),
  116. data.url("js/common.js"),
  117. data.url("js/google.js")
  118. ],
  119. onAttach: function(worker) {
  120. worker.port.on('load-results', function(query){
  121. var url = 'https://api.duckduckgo.com?q=' + encodeURIComponent(query.query) + '&format=json';
  122. if (ss.storage.meanings == false) {
  123. url += '&d=1';
  124. }
  125. if(prefSet.prefs['dev']) console.log(url);
  126. Request({
  127. url: url,
  128. onComplete: function (response) {
  129. if (response.json) {
  130. worker.port.emit('results-loaded', {'response': response.json});
  131. }
  132. }
  133. }).get();
  134. });
  135. worker.port.on('get-options', function(){
  136. worker.port.emit('set-options', {'options': {'dev': prefSet.prefs['dev']}});
  137. });
  138. }
  139. });
  140. pageModBing = pageMod.PageMod({
  141. include: /^https?:\/\/www\.bing\.com\/.*$/,
  142. contentScriptWhen: 'ready',
  143. contentStyleFile: data.url("css/bing.css"),
  144. contentScriptFile: [
  145. data.url("js/jquery.js"),
  146. data.url("js/common.js"),
  147. data.url("js/bing.js")
  148. ],
  149. onAttach: function(worker) {
  150. worker.port.on('load-results', function(query){
  151. var url = 'https://api.duckduckgo.com?q=' + encodeURIComponent(query.query) + '&format=json';
  152. if (ss.storage.meanings == false)
  153. url += '&d=1';
  154. Request({
  155. url: url,
  156. onComplete: function (response) {
  157. if (response.json) {
  158. worker.port.emit('results-loaded', {'response': response.json});
  159. }
  160. }
  161. }).get();
  162. });
  163. worker.port.on('get-options', function(){
  164. worker.port.emit('set-options', {'options': {'dev': prefSet.prefs['dev']}});
  165. });
  166. }
  167. });
  168. }
  169. function destroyPageMod() {
  170. pageModGoogle.destroy();
  171. pageModBing.destroy();
  172. }
  173. function installToolbarButton () {
  174. toolbarButton = require("toolbarbutton").ToolbarButton({
  175. id: 'ddg-toolbar-button',
  176. label: 'DuckDuckGo',
  177. image: data.url('img/icon_16.png'),
  178. toolbarID: "nav-bar",
  179. forceMove: false,
  180. onCommand: function() {
  181. var mediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
  182. var document = mediator.getMostRecentWindow('navigator:browser').document;
  183. var navBar = document.getElementById('ddg-toolbar-button');
  184. popupPanel.show(navBar);
  185. }
  186. });
  187. toolbarButton.moveTo({
  188. toolbarID: "nav-bar",
  189. forceMove: false
  190. });
  191. }
  192. function uninstallToolbarButton() {
  193. toolbarButton.destroy();
  194. }
  195. if (prefSet.prefs['zeroclick']) {
  196. loadPageMod();
  197. }
  198. prefSet.on("zeroclick", onPrefChange);
  199. prefSet.on("ddg_default", onPrefChange);
  200. prefSet.on("toolbar_button", onPrefChange);
  201. function onPrefChange(prefName) {
  202. if (prefName == 'zeroclick') {
  203. if (prefSet.prefs['zeroclick'] == true) {
  204. loadPageMod();
  205. } else {
  206. destroyPageMod();
  207. }
  208. } else if (prefName == 'ddg_default') {
  209. if (prefSet.prefs['ddg_default'] == true) {
  210. if (browserSearchService.getEngineByName('DuckDuckGo') === null) {
  211. browserSearchService.addEngine(data.url('search.xml'), 1, data.url('img/icon_16.png'), false);
  212. // it's 1 (because of magic and you need to read the Firefox code
  213. // to understand it)
  214. }
  215. contentPrefService.set("keyword.URL", DDG_URL);
  216. } else {
  217. contentPrefService.set("keyword.URL", "");
  218. }
  219. } else if (prefName == 'toolbar_button') {
  220. if (prefSet.prefs['toolbar_button'] == true) {
  221. installToolbarButton();
  222. } else {
  223. uninstallToolbarButton();
  224. }
  225. }
  226. }
  227. function loadIntoWindow(window) {
  228. if (!window) return;
  229. let urlbar = window.document.getElementById("urlbar");
  230. if (!urlbar) urlbar = window.document.getElementById("urlbar-edit");
  231. if (urlbar) {
  232. let oldSearch = urlbar.getAttribute("autocompletesearch");
  233. urlbar.setAttribute("autocompletesearch", oldSearch + " duckduckgo");
  234. urlbar.mSearchNames = null;
  235. }
  236. }
  237. function unloadFromWindow(window) {
  238. if (!window) return;
  239. let urlbar = window.document.getElementById("urlbar");
  240. if (!urlbar) urlbar = window.document.getElementById("urlbar-edit");
  241. if (urlbar) {
  242. let oldSearch = urlbar.getAttribute("autocompletesearch");
  243. urlbar.setAttribute("autocompletesearch", oldSearch.replace(" duckduckgo", ""));
  244. urlbar.mSearchNames = null;
  245. }
  246. }
  247. var windowListener = {
  248. onOpenWindow: function(aWindow) {
  249. // Wait for the window to finish loading
  250. let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
  251. domWindow.addEventListener("load", function() {
  252. domWindow.removeEventListener("load", arguments.callee, false);
  253. loadIntoWindow(domWindow);
  254. }, false);
  255. },
  256. onCloseWindow: function(aWindow) { },
  257. onWindowTitleChange: function(aWindow, aTitle) { }
  258. };
  259. function startup(aData, aReason) {
  260. let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  261. let enumerator = wm.getEnumerator("navigator:browser");
  262. while (enumerator.hasMoreElements()) {
  263. let win = enumerator.getNext();
  264. loadIntoWindow(win);
  265. }
  266. wm.addListener(windowListener);
  267. registerComponent(DDGAutocomplete);
  268. }
  269. function install() {
  270. }
  271. function shutdown(aData, aReason) {
  272. // if (aReason == APP_SHUTDOWN) return;
  273. let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  274. wm.removeListener(windowListener);
  275. let enumerator = wm.getEnumerator("navigator:browser");
  276. while (enumerator.hasMoreElements()) {
  277. let win = enumerator.getNext();
  278. unloadFromWindow(win);
  279. }
  280. unregisterComponent(DDGAutocomplete);
  281. }
  282. function DDGAutocomplete() { }
  283. DDGAutocomplete.prototype = {
  284. contractID: "@mozilla.org/autocomplete/search;1?name=duckduckgo",
  285. searchURL: "https://api.duckduckgo.com/%SEARCH_TERM%&format=json&no_html=1",
  286. resultsURL: "https://duckduckgo.com/?q=%SEARCH_TERM%",
  287. _searchString: "",
  288. startSearch: function(searchString, searchParam, previousResult, listener) {
  289. if (!prefSet.prefs['addressbar_zci']) {
  290. return;
  291. }
  292. this._searchString = searchString;
  293. this._listener = listener;
  294. if (!previousResult)
  295. this._formHistoryResult = null;
  296. this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
  297. var submission = NetUtil.newURI(this.searchURL.replace(/%SEARCH_TERM%/, searchString));
  298. var method = "GET";
  299. this._request.open(method, submission.spec, true);
  300. this._request.onreadystatechange = this.onReadyStateChange.bind(this);
  301. this._request.send(submission.postData);
  302. },
  303. stopSearch: function() {
  304. if (this._request) {
  305. this._request.abort();
  306. this._request = null;
  307. }
  308. },
  309. QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
  310. _isBackoffError: function (status) {
  311. return ((status == HTTP_INTERNAL_SERVER_ERROR) ||
  312. (status == HTTP_BAD_GATEWAY) ||
  313. (status == HTTP_SERVICE_UNAVAILABLE));
  314. },
  315. _noteServerError: function SAC__noteServeError() {
  316. if (prefSet.prefs['dev']) console.log("server error");
  317. },
  318. onReadyStateChange: function() {
  319. if (!this._request || this._request.readyState != 4) return;
  320. try {var status = this._request.status; }
  321. catch (e) { return; }
  322. if (this._isBackoffError(status)) {
  323. this._noteServerError();
  324. return;
  325. }
  326. var responseText = this._request.responseText;
  327. if (status != HTTP_OK || responseText == "")
  328. return;
  329. var serverResults = JSON.parse(responseText);
  330. if (this._listener) {
  331. var results = new DDGResults(serverResults.Heading || this._searchString);
  332. var FormatResult = function(aType) {
  333. return function(aResult) {
  334. results._results.push({
  335. value: aResult.FirstURL,
  336. label: aResult.CustomLabel || aResult.FirstURL,
  337. comment: aResult.Text,
  338. style: aType,
  339. image: aResult.Icon ? aResult.Icon.URL : aResult.Image
  340. });
  341. };
  342. };
  343. if (serverResults.Answer) {
  344. FormatResult("Answer")({
  345. FirstURL: this.resultsURL.replace(/%SEARCH_TERM%/, this._searchString),
  346. Text: serverResults.Answer,
  347. Icon: { URL: serverResults.Image }
  348. });
  349. }
  350. if (serverResults.DefinitionURL) {
  351. let title = serverResults.DefinitionSource || "Definition";
  352. FormatResult("Definition")({
  353. FirstURL: serverResults.DefinitionURL,
  354. Text: title + " - " + serverResults.Definition,
  355. Icon: { URL: serverResults.Image },
  356. CustomLabel: serverResults.Definition
  357. });
  358. }
  359. if (serverResults.AbstractURL) {
  360. let title = serverResults.AbstractSource || "Info";
  361. FormatResult("Abstract")({
  362. FirstURL: serverResults.AbstractURL,
  363. Text: title + " - " + serverResults.AbstractText,
  364. Icon: { URL: serverResults.Image },
  365. CustomLabel: serverResults.AbstractText
  366. });
  367. }
  368. serverResults.Results.forEach(FormatResult("Search"));
  369. serverResults.RelatedTopics.forEach(FormatResult("Similar"));
  370. this._listener.onSearchResult(this, results);
  371. this._listener = null;
  372. }
  373. this.request = null;
  374. }
  375. };
  376. function DDGResults(aSearchString, aResult) {
  377. this.searchString = aSearchString;
  378. this.searchResult = aResult || Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
  379. this._results = [];
  380. };
  381. DDGResults.prototype = {
  382. defaultIndex: 0,
  383. errorDescription: "",
  384. get matchCount() { return this._results.length; },
  385. getValueAt: function(index) { return this._results[index].value; },
  386. getLabelAt: function(index) { return this._results[index].label; },
  387. getCommentAt: function(index) { return this._results[index].comment; },
  388. getStyleAt: function(index) { return this._results[index].style; },
  389. getImageAt: function(index) { return this._results[index].image; }
  390. }
  391. function registerComponent(comp) {
  392. let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
  393. if (!comp.prototype.classID) {
  394. let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
  395. comp.prototype.classID = uuidgen.generateUUID();
  396. }
  397. if (!comp.prototype.factory)
  398. comp.prototype.factory = getFactory(comp);
  399. registrar.registerFactory(comp.prototype.classID, "", comp.prototype.contractID, comp.prototype.factory);
  400. }
  401. function unregisterComponent(comp) {
  402. let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
  403. registrar.unregisterFactory(comp.prototype.classID, comp.prototype.factory);
  404. }
  405. function getFactory(comp) {
  406. return {
  407. createInstance: function (outer, iid) {
  408. if (outer) throw Cr.NS_ERROR_NO_AGGREGATION;
  409. return (new comp()).QueryInterface(iid);
  410. }
  411. }
  412. }
  413. function truncate(text, limit, append) {
  414. if (!append)
  415. append = '...';
  416. subtext = text.substr(0, limit);
  417. subtext = subtext.replace(/\s+\S*$/, "");
  418. return subtext + ' ' + append;
  419. }
  420. result = function (text) {
  421. if (text.length === 0) {
  422. throw ("Nothing to show.");
  423. }
  424. if (prefSet.prefs['dev']) console.log("input: " + text);
  425. var request = new xhr.XMLHttpRequest();
  426. request.open('GET', 'https://api.duckduckgo.com?q=' + encodeURIComponent(text) + '&format=json&d=1', false);
  427. request.send(null);
  428. var out = [];
  429. if (request.status === 200) {
  430. var res = JSON.parse(request.responseText);
  431. if (prefSet.prefs['dev']) console.log(res['Type']);
  432. if (res['AnswerType'] !== "" ||
  433. (res['Type'] === 'A' && res['Abstract'] === '') ||
  434. res['Type'] === 'E') {
  435. out[0] = res['Answer'];
  436. out[1] = 'http://duckduckgo.com/icon16.png';
  437. } else if (res['Type'] === 'A' && res['Abstract'] !== '') {
  438. out[0] = res['Heading'] + ": " + res['AbstractText'];
  439. var source_base_url = res['AbstractURL'].match(/http.?:\/\/(.*?\.)?(.*\..*?)\/.*/)[2];
  440. out[1] = 'http://duckduckgo.com/i/' + source_base_url + '.ico';
  441. } else if (res['Type'] === 'D' && res['Definition'] !== '') {
  442. out[0] = res['Definition'];
  443. var source_base_url = res['AbstractURL'].match(/http.?:\/\/(.*?\.)?(.*\..*?)\/.*/)[2];
  444. out[1] = 'http://duckduckgo.com/i/' + source_base_url + '.ico';
  445. }
  446. }
  447. out[0] = truncate(out[0], 40);
  448. return out;
  449. }
  450. var numClicks = 0;
  451. exports.main = function(options, callbacks) {
  452. if ( (options.loadReason != 'startup' && options.loadReason !== undefined) ||
  453. (require('self').loadReason != 'startup' && require('self').loadReason !== undefined) ) {
  454. if (browserSearchService.getEngineByName('DuckDuckGo') === null) {
  455. browserSearchService.addEngine(data.url('search.xml'), 1, data.url('img/icon_16.png'), true);
  456. // it's 1 (because of magic and you need to read the Firefox code
  457. // to understand it)
  458. contentPrefService.set("keyword.URL", DDG_URL);
  459. prefSet.prefs['ddg_default'] = true;
  460. }
  461. }
  462. if (prefSet.prefs['dev']) console.log(JSON.stringify(options, null, ' '));
  463. startup(null, null);
  464. // Create a new context menu item.
  465. var menuItem = contextMenu.Item({
  466. label: "Ask DuckDuckGo about that",
  467. // Show this item when a selection exists.
  468. context: contextMenu.SelectionContext(),
  469. // When this item is clicked, post a message to the item with the
  470. // selected text and current URL.
  471. contentScript: 'self.on("context", function () {' +
  472. ' var input = window.getSelection().toString();' +
  473. ' self.postMessage(input); ' +
  474. ' return "Ask the duck"; ' +
  475. '});' +
  476. 'self.on("click", function(){ ' +
  477. ' var input = window.getSelection().toString();' +
  478. ' window.open("https://duckduckgo.com/" + input, "_blank");' +
  479. '});' ,
  480. image: data.url('img/icon_16.png'),
  481. onMessage: function(msg){
  482. res = result(msg);
  483. if (res !== null && res[0] !== '' && res[0] !== undefined) {
  484. //var x = '';
  485. //if (res[0].length > 40) {
  486. // console.log(res[0]);
  487. // x = res[0].substr(40, res[0].length)
  488. // res[0] = res[0].substr(0, 40);
  489. // this.label = res[0];
  490. // menuItemSecond.label = x;
  491. //} else {
  492. // this.label = res[0];
  493. // menuItemSecond.label = ' ';
  494. //}
  495. this.label = res[0];
  496. this.image = res[1];
  497. } else {
  498. this.image = data.url('img/icon_16.png');
  499. this.label = 'Ask Dax about ' + msg ;
  500. }
  501. }
  502. });
  503. //var menuItemSecond = contextMenu.Item({
  504. // label: 'second',
  505. // context: contextMenu.SelectionContext()
  506. //});
  507. //
  508. if (prefSet.prefs['toolbar_button'] == true) {
  509. installToolbarButton();
  510. }
  511. };
  512. exports.onUnload = function (reason) {
  513. if (reason === 'disable') {
  514. contentPrefService.set("keyword.URL", ""); // reseting the location bar search
  515. }
  516. if (prefSet.prefs['dev']) console.log(reason);
  517. shutdown();
  518. };