michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const SEARCH_ENGINES = { michael@0: "Google": { michael@0: // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.; michael@0: // it will be scaled down as necessary on lower-dpi displays. michael@0: // This needs to be defined in a single line to keep the JS parser from creating many michael@0: // intermediate strings in memory. See bug 986672. michael@0: image: "data:image/png;base64,\ michael@0: iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ\ michael@0: bWFnZVJlYWR5ccllPAAAGrFJREFUeNrtfHt4VdW172+utZOASLJ5+BaIFrUeXkFsa0Fl++gDnznV\ michael@0: VlvFxt7aqvUUarXtse3Bau35ak/rZ9XT26NtfOvV6wFET+FYCQEKWqsQIT5RCAgSXnlnrzXneNw/\ michael@0: 1lphJSSQ8BB7bub3zW+LO3uN+fiNMcf4jTEX0N/6W3/rb/2tv30smtnXB3zmRi2FQakxQNKX3WkW\ michael@0: 9S/tgW3HLpmQM543A0BWVSHMYGIwOTDxzxrOf3/RQQfMZ2/SLAvKhTFVBGUqKFONH2QAzwOMF38a\ michael@0: wHhYZAxWAqhe/iszp3+b970d/sInc57vz/J8L2eMB2MAEYkBQ6DQ3dRw4dq7AUjcP3rAfPZmLWXC\ michael@0: LHKoIAcQAUxaB5EaEfc6AEBhjDEwmcx43/fO9HxT4vkReBIAAZgjgodW3NcPnn1sHgD/iHknn+0d\ michael@0: 6s8XEUhsXXac/34WAAGw8afuT8GZ3X055YeSJcIsG+pMZwFn0UihezRofPt3G54f/0E8cNMN+Myo\ michael@0: 8jVTCgYd823PLzrPeIBnABiUQ1F+UoWsVOYb33mkoKp/7/dKyT0AGc47X4s0sjBEoLxbBqAQAMfW\ michael@0: Rfe38B4BM+VHUkYOs8mi1FrABbK4dcvK73zwp1M3xYPOxANKBqbpCdXNGb0UwPKRF74xpfDQ0t+K\ michael@0: 54+IvlKoahmAhaO/mv/ZmicG3tqPgT61ZM2dZMQJOYhIdByRM/F3dCCOox4Bc3oEliqyyNoQCPPu\ michael@0: sXceKZqRsigu7pwaWBowiRb46+f9Q1V2wl1nDx09/R7jF30x9adNlN8yPx4DHwht+B/cBIBoRqeI\ michael@0: E4hE/oshTcB0wNbT6/o/zrhFyohR5ZxmrVWE+fDxdx4puhGAH4OkPe5B6pykeJAc/7cDEMZ/095Y\ michael@0: 870P339m+BXs2v4kbCFsm9u2vnpJ3bzR7wAo2B/R2v+PjSnyXcRxtOLUSXFxwAFz5i2SZUIVO82S\ michael@0: BWye/vLOIwNvjL8OYqCEfXCmJAZPHkC7sK1REbj2+lmbq86qTVmmfuuyN2cTiREWKCvACgml9kDL\ michael@0: 7HQksehsZmSdA6yVpsa6P38v3swg7m4vN1dGXrThKGP8yS5fP33j/LEvxKDbl2f2A0YFCtkZQDOa\ michael@0: PjLAnP4jrmBGjh1AVhG2ttxfX33++vjY2eeNXf/siLUAzgEwMJZrY2vF/Vu/t4BRqCqgCmj07wMV\ michael@0: HXUCzJQfUlZE72ICnANcqNj21h8eiK1AX46gXh29KT9H+rd9XxBjYGCgig7QHOgjPgMAKigXQZYp\ michael@0: si4uCOc3v35zY2wF9ufGSgxA7fdd9g8ho9ol4P4ojiQWnSUMMANECrJNy1NWYH8eGfsEvJbLv1IK\ michael@0: 1XIAUwEtA0xplJMwjcaYlTDeShg8dOgjj6/cJxNYfWIWkHJoh5yyjkSZ8RbB89YBZq4/pXafGeuz\ michael@0: b9WciXJxo2B2houqgAjABJCLOwFMqFv57+bBxMIAJm1det3avnl1OYCLAeSgWhofaY1QXQSRuYc+\ michael@0: /OiD3QLmUzNdqTBKhRVMADsF5beuToXJB90KtFz+lVIVniXOVUAUqjpXVB4WwPjGTPB8/0zjeTnj\ michael@0: ezl43szmKy6vNkDF4MeeXNc3oJyUhfAMkJsJkSxUVrLos6o6z/O8Ucb3phrPzyHKeVTwkpPXseg3\ michael@0: Cqe+1SfG+swfaw6KGTAoJ5eyGF3IBeEIJB2AcXxb0FI/L45uFQBMGiu6Z3ai9eqrclBUClFWVatV\ michael@0: 5GERNT5wEVQnQLUcIuVNX75kFjn60rA5c1d0AoywlkcxfdwZ2LSgbOmBZAv70povu7RcyFUqcZYd\ michael@0: Pbxix44fnLv8pbYUOWh+P3ZM9uJRo34xoLDgq8b3YTxvqhqsaPzyJTdmn36msjdyqPqkMhWqBFGZ\ michael@0: MtV8uDX4zMjp2zemyEoPgGn4zyOvGzy48A54GcD3Sz1jFrqqE+4uOOvdmb0ASlYEs5mQE9afUdhy\ michael@0: 0yv3lHzwya/8ZcjgI0+5yssU3QKYkgQ4Ivp60LL1n8kBQfOWuvdnj6uLldgHQKoKxU7HV/eg2y1X\ michael@0: XXmXEs1U0ZVb29o//4k5c5P5eQB+s+68aVeUFBTcCxUoS6kRWfjhueecc9SfX3ytA9QTr7eVACqY\ michael@0: FDYEwnbB2qcHHg6gLY6ODhpomi77coUyVaojhKH9+ZHzF/wqXiztEg34APxNX/jCvQOLCi83fpy8\ michael@0: UsCJXHLYnGdn785S0uKTyyBUBXJZcW5x4bSN56ciyLQcD4Bf/+ThVwwbUvRb+JkoswqAWX5b9Lm1\ michael@0: M3uSM/UnUiaCKiZk2blvvnxX0ePxuBNAmpMur51wyLBPzjVeBBoVwIXBk6vuP+SG+LkcuwkWAA96\ michael@0: /JjZKnKxkACkkFb5Nztz220xX9bJlWi+6opKFalQlpqlmzZNu6B6SaJ0knKJ/DW5qd8p8TO3x6AB\ michael@0: qza1EE06cdmy9wDAY5LjmBTMkQnUnZ42H0ywNF52aU6FK4UY5NySI+cv+E3MCnMM5HyqtwFoO3rB\ michael@0: gmuDMFjGjiCOIEQwzH9c+7lzju+JTaYlJ2ehUqXMWWFqeurFxqsAFMVf25Ss9kTOEZdvebClJbxT\ michael@0: yUGZoEzwlL/b9tzRX+pOztSfSBZApSqyIrL45buKnkaUJEzLCN5+csxr+ab6fyILkI2OIZYBlx9/\ michael@0: 2bYvpLgw2+EqKLKdwoceVKJp+tfuEpYKZcaW1tZbLqheEsbj3GV+oxdV3x0GwQZrHUIiWKIST3Vm\ michael@0: DG54zFrKrBBWiGgSyx9Uv6Xh0n/MKlGlOII4h80trQ+kuJt8HGklZHg6FZF/Y/uOb7O1YOvAzkGt\ michael@0: Kxmoehe6SYNEpkErwZIFC4I2fuLKf2tLtDOPzumPhA6wAPJDLt1yuzjaAEcAMUCMApXfvPP7IcO6\ michael@0: gkYFs4RRpgy49qanUsAPu/T8W48e/YwL6S/kYtBYwM8U/yu6KVlQUShr9CkKyK7b1vDVy0qVeaYy\ michael@0: gaxbdeK85/8a/z7sYR3zgXM1gXUInEPoCEw8PR6z8YQxaidQPh6RrgrPEOZS4chKjFuydEEKFD1x\ michael@0: QgrAnfO3V98Jw/B5dhFgmByU+MK/nnrq6K6gcQtPyqlIubJAibCxPv/fsVVNgCI9yGEAQdBq71NH\ michael@0: UEdQIoBo5PBBeklazuQfSpYFM0UAFsDmd2yMf9+1XkUT3otc8AiRwpFChCBCI0detGbSLtYr5uw6\ michael@0: tk26XctZwgxhRt65ZSmr1t389M1Jk85wzKcHRAiJkCfasDnI/0sMGN+jlLMrAigMhp0+f+TBBIw4\ michael@0: milEYOcQBHZZAoZeEIgKgIIgeJbD2MqEFhxaDAFmdAWMisxQFigzlAUnX9e4rA9yeHuTna3koBQB\ michael@0: RogxwOPvxNbQAAA7VHQEFKSQKEFIu4lA5d3HiiuFNB4XQZlhUHBK11QO0oRdD7ouROVCkeJZG7ak\ michael@0: /KBOYHlz4sTy1WVlVY5oYego2+bs82+3tFw6YcVrp01dteqpxNfyhKQuGlxCMSsKBh570ABT/8XP\ michael@0: 5dhRVpyDWAd2Ns0O9yrhWdfcMpvCEByEoNCCwhBgvgBdM+PM5TH5FPW+1ZLo8de2viehe12dhVoH\ michael@0: OAtDPO61O4o+kYCTnE5wVuGsxlzKHul7BUDKdomKgwpB2QHAyNiP2Dl+0Z2WRXZ9YP0F55WJczvX\ michael@0: 0jp09U3fLiurWD1+/NqQaHZIVNbu3O1vt7aM+fSqVRWXvPvu0pRldwAkQ5brjO+NMh0kgMIvGjYZ\ michael@0: wIKETPxIrYt1U5M8iThKJil9yZGc++ab298dP36Jb8wZohqhQHRErKEeAA6fG5FT5yIlYYI6tzfO\ michael@0: vtiQni3MYDw0ChqEgUMyejyAdwGwDeW4ZI9FAGQOmwzgv/cERmZbDXhnKBNUGMJkUhGVduSSJJ1P\ michael@0: 6rw8HIalJo7ilBkchgCgL48fVzLceDc4kZnWUdap1AQi10x+660n4jXyk1M7ZXEZgHhMUkMO4Njp\ michael@0: hQGMf8h56Fx++ZE1a+1xZC2Szjs3sk9uUEhUbSMvP3LeyOGZ0tKJiearo1J1DHVRPYmS7JUcG2g1\ michael@0: pxxUsooBnpmQWAOb10YbKGygcKFCZOC0XqxrRKokCBQG5euX77In2k1P+2hhWEZBAAoCuCCEcW7E\ michael@0: 2xMn/m6oYo0jyjnmuc3Off6UN96YMvmtt5LILSmQ61r3xAA0I+xqPBiIejAd1f7e2MPPfvm4LQs/\ michael@0: 89a+bP6nZuSzfsaU+T7g+UBixYQVRFGS01kFO22srRy0EgA4CEvFRHS3MANMY/fGbybmlQqAFSBV\ michael@0: sCp8kWwCGA5dqefFShnnRV77ecHYU37iXuqLoB0tsuIo34v3NfJR1GlJsrnOuiXGy1y8k+rwxh57\ michael@0: 3srSD/6rbLdra7yMqgjUCGAULR8uWr0LJPYAGApCeCbKNygLPKIxJ65YOSU+YpLUUCYGiqBzQVy3\ michael@0: Ft1zbevnJl60UARqACgcVDo9ZZr63Mqua68QxlpmrWJC1FmrmLSKCFVktcpZrbKhzg4D26E5Lgjg\ michael@0: 8vnoMwwh1hU/dvTRo/qcDyJqcESw5Dp6o3XNHVrqLDSubAdFjuXwwWZcX+Wc9APboKxQUoiLurXa\ michael@0: IYfCpjlCDsoxZ6OCouLRt+xpbY3nA8aDMR6E2+9vffOWxl02cQ+Bbdjevt7l83D5ABRaKNHYO484\ michael@0: YmgMkoJ4jElCOL8Lz9NN87YumrRDxc2DElQZKgIVhZcZcO1hZ74wtK/H0thvtuXGXdM2S0S/ziQ1\ michael@0: FPJiG7pHwvbgDhtKnQ0VNhCEeUHQLmiuf2fymieGvJGY8DCfX+yCEC5xWIlwtO+P6+s4VESJGS4+\ michael@0: liwxKjZ/2FGRZvPhYgktxEZdHWOAr2P34ihWIQWTgJ2CnWJbo9Ymz1g/5+h1QsF9wgKJ19Z4hV87\ michael@0: 4fKNE3cnx8v4V8H4UOjqhvce+zW6qdWVlOvSjQsDlw/WUT4A5QNQGIJDizMPHXR+CiRBb4GSzlYr\ michael@0: 26Z7vYKSC42nUOPBqA9VU1I0ZOJPEYWj1NvVW/3AoEUAFgO4IzZ1hYk2jf9WUw7IjCIXHUVhXrFp\ michael@0: /sQtKZPIoXXr/PjoSkZeoHo6gP/bFyeciECqcHG3IrXp37a2SF3xQNPxRAXgq5nS1bHsDWCYALYA\ michael@0: u+h0W/impI8Pad9ec/vAoWVTjV84Nsn5FAwcvmDMN5rOqf1jyatdHzjuGjvThloKYH3b5qVXt775\ michael@0: 44ZuN1QEKknF3a6ImfDee4tWjBrV6R5Qoeq1AP6Avaxx8gDolhdPXAh2qzQmZFQ4ZhALrj/mvLpT\ michael@0: +qhxya0BP5VVZQBkA6jNR0AJ2xUUcjKGjsx4k3PVYUwaJU6rJ3reLiHlHppjBjF3fLYSzU/noEZ8\ michael@0: 3611VusoVJBVsFWAdezim/3jemSFe+SNIsvCpAhCXf7TBZI+PnTr4nO2t2xcME3ZroYKIouEEqDo\ michael@0: xfHfav/GxOttFgBOucGWll0XVqrqXYDWNLz3aG7bsovWp4i2TvkhScLqNBezq/M/zxLBxV2Yx/75\ michael@0: yCPP6usc04CJ+B3bcLMwQTiK+0UIwgz1ip8+4pyaYX0x0SnWMkjnYGygkm9nBO0MGzoI2TTDyQBw\ michael@0: 7ubNawPmeZYZNt5wZhrxX8OHX9yXSTJzGcVgIWasbs8/hc7XRzXM670cg0Vs5H+MHm6u74ucrb/K\ michael@0: lAlFPoySoqFFn+rm+OCGV762df2cYWe4fP0M5qDWhoowRIm1/h+s1YZx3wrVOV1LDhXMaGzfXntF\ michael@0: 46vXtMQRS/clsqRRT9SNd0GMBo6edRStZbKeg4D//ciQIcP2CTDbqsdVKQePq1JMFkXxv4qO9AaM\ michael@0: fPGoaeuG9kXp0LkU0wGgMFC1gYAdAeyg0m3IrE3W3mtTvodjRpHq9X3xL4h5Qsq63P/z9ra6LqSc\ michael@0: vvmBPkwOTex2lnf4wNee/47fa99NGGVJ8Zl1qP3UPfwkdr15mDDV+Y3Pf+Kh9c9kz9pee89J7dve\ michael@0: vaRt+7qLbVv47y5UUKggp3BB/okNz0/aHI8332OaIgELxWDpptQtt6X+Qcu03nVYGQYxjxzl+7/e\ michael@0: GyvjdYrCtv31JiW7QTjy6qWj83jF4AeP/MLaodiHRtZBXAihEEIWkq4eSgGmvKGhqpX5d1YEVhiW\ michael@0: BaI6Zf6QITN7s5ELhw4tZZavkwhIZMOC1rZfo5s64nPv4+1NzXot2/hYiqKckglH4/7eRojCOosp\ michael@0: St6u2ijfS1Hv3I0SdVy5aam9ecumBeOqN8w7aRkxSlMVdRDmRHa4m5xWPKPEusUA6maIrcy/cCKw\ michael@0: InASKaCoXrlo2LAH+xpMpAEjLauu2ObaNnxVmZqUHaI8SaR+KnIhTPHCo6ZtOn6vk4qUPNNGnV2P\ michael@0: J0ptENweMq92zHBMcMwwIrfMLS6etKdJEnMlCYOZm9YE4dUPkWvsIUckJ/+SZwd5PCEOEBc5rh7j\ michael@0: grqf+VfvSc7mO/xZSihVAra3YMY/PqqrUhZVe7C8yRHTBqAVQJuQN5idgJ2ASQAz4PJjptWevKc0\ michael@0: RZQ0TQATRWDd/dmFDQ2VeaLH0z4dRVTK9EXZ7IqFJSXH7W6eLw0blntp2NAydGOSqPGVs/5mW9Zc\ michael@0: JGKbRSxELIRDCFuIuAmiBa8eMW37rcdc1JDtM+3PYdSp43k9/ulPgmDrsnz+vFBktRWBZYEVKSlU\ michael@0: feH5wYPP7u5Hfy4uzi4oLq50IjkSaXrf2vIfBPnV6PlKiwKg0XfyNe2BPkmJ8+oUGeh/bLjNu7En\ michael@0: 0Gy+w5sppLcyKRra9IZJ98hTvciop9MPSSFUwGTnEjHICsgpyKHYHzjquWMvrJ+wewUENPFjCIAx\ michael@0: k3uStyIMbw5FVieWJvJpBE5kgqq+X1VcPGdRcfHMxSUluSUlJbmlUZ+1tKRkLRGVnrZ9Rw12rSLt\ michael@0: sDpFg8vmfbpw0HH3wcuMMSaiao2XAbwMjPFhPL/ReN6DfsY8tHHekN0WXR929vqsCpWruFshPEqF\ michael@0: o3IyADuWTxgea1rYTbRVeEMmc+SnCwp+OcB4l3kmLq0D4BnzkA/MMUBjvDMXC1DBqlkCFr9N9E//\ michael@0: HIZpPyDsQVuTFwsMfP273k8GFeLbvo9izwe8DGA8VMPgIc/D2piALlPFDGWUMqNuazOun/RbeQU7\ michael@0: L/zl0cfC+SPOXjG84NBRawCvJNoSE7PiBgr5Xx/MKf7jLnzIbUPKlHVF5C11KgJfD9+shY8Vxjd3\ michael@0: 0780rEvP8bFDDvnVQGO+lU5MeTDwzM5aTbOzNyrw/XNbWx9JFLknk+sjqjobUHJq9XS/cNj3jZcZ\ michael@0: Ac9PwBIDyAeMD2O8RhhvpTFYqYpGqMQOM2UhlFOhsvjfgNJ6ofxyoZaXbHPt8mDNjDU9ACYBbyGA\ michael@0: AT/KZEZ/MpO5qciYyRlgROeJGSh0nQCL21Ufmx4EL8dMpqScRt4DFVAAYMCtORx+0Rhz7aFF+GJB\ michael@0: BmNM/JKklGo1KlBtHZ474U79P9hZOZcQYb0unD/mwu05qADCZwE4C8Y7I3kTk4kFx+mUuzfMKf5e\ michael@0: +rn+rUMq4PR4hFII0gw0xpdvGAWGoDqHf9m8IuV8m2Qtf1pQMPok37+50JhpHlC8EzwRcAzwOqs+\ michael@0: Vkv06I+da04nInd3RvuxgCIAhcUTF5zvFQ79oucP+Cy8zIjE6qQnt5Pviu5IqAogVKNCNSrBUte6\ michael@0: blnrqi/Vo3O9rI3Pc7cbP6sgGQcAf7rvl3zK908uBKjAGK5jrrmNKKHj/RS3E6L3V2USLUzkZAB4\ michael@0: i75pTivwwQMyoKYQ685+QOtScvzUHPbIlJ54ZVsuDPTrZDmnQqUQggo1qkoNRDyFeJ6XGQfjF0fW\ michael@0: 3O9YWxW6adNzw36Dzm/JKEJ0k7QgtfiSygd1vSrkdZ3jlb6fneT7Y+MN1xrmVX9gbkw9q1MdsemF\ michael@0: U5wkpwqSRSw49gfZAcPPHOsVlIww/sBjjPEVnqfGZEQlWKVCjWK31TW/dv56pCruU126TGxPl+US\ michael@0: IrAgNQ7TQ+pNukQqfalLNimApvMt6CZMTvsiu3VOJ17XnrNWZ9m85oK8Qmz4sFB+CeXrF29dfOqG\ michael@0: 1PwKs6fOKyvKjrnb8wrHGD8TWfCOEoX85zb96dgXY9leN2NM+y3SJZG4u7XsSldIykFPz09NHxbR\ michael@0: T2U3M11AsKf8aRqtnBqQoG91oWkGOS0/XaQo2Pf3u5mUDK9LukD7Mv5Tv9teSQ4VzipsINUtW9Zc\ michael@0: t/mFiRu7WbcOuQNP+MXQ4hGX3mEKBl1mjB9bbwAqSz6cf+TZ8Qaabta/u6hM92ItpZs5dvyor5R/\ michael@0: dwvp9QAa6eFzfxRlpVMk2mXh93czeyPn1Bn5ShWtYAJsyEve+OPgC7Hzmgx3USDtejQedlbtDX7h\ michael@0: 0Ns6HChV5LcvP7rpb1+qx/690dHrtewL05c2c7ZLtrM91fOpDGjXyvT9+WYBPQAg3NPcey1n4vVt\ michael@0: FUJSIfGNjJZNy2ekkqzpazIJOefSoTaA9q1VY+5Wbvs9NAoYVBkFh5Sesi9lJ/u6lt5+WETpoi2M\ michael@0: PpZU/k9szmKGtVGRWBjQ6g3zP78pxfSGKb+tJ4LPAsi31S/+uXCUlVZmCIc+DlI15L4Cpr/1FA1d\ michael@0: 0VLqAilzgcCGChdQc5eoTXqpkNS66hv1YLsUElURiG1sOZj7lunf3v3fwlBKjRfX9EjEHKcscV98\ michael@0: D40zRKIqgEpz4yvTVnfjU/VbmL/r4yhwTTbPCNsZNi8g50/OnvbCsXu5wQqVURCBuOb7seu98n7A\ michael@0: /L23Tc8NX8mW6pL73UoOhYPH/GJv/I7Dzlqbg5pRUG1q++A//+Ng+4f9gDlATVzLHfErZiHioKrn\ michael@0: H37uhgeG597sdYnIYeeszypQqQawre9dHNbd0Yj9/5KnfsB8DJpuXXj8Q+ryj3dUZglD1Uz3MsWv\ michael@0: HX7uh1fv6QGHn7upAmrWQpEV2zSt+bVptamw+6C9VaP/hcoHrvkABgydUjPLywy6Oboh6HW6PgLj\ michael@0: LYqStqYRQHKDMQflMhXOQrnata27tvGvufrEn8ZBfmdPP2AO7NpmAAw85B8qTyjKlt1svAHTjPGL\ michael@0: k4w0jAcTAyllnBoh9Kxw/tEdS8cuT0WyH4vX1PYD5qMBzQDE2eFDxz09zsscWuwVHX6a8YwaFAiM\ michael@0: NAkHr4vdUdf82rQN6JwnSl4N4vAxeKdxP2A+mjXuKTvcXcY9TdOnyxPk4zKZ/vbRAqe75C3QfZZY\ michael@0: 0P/y6/7299z+H4QrdGsoib8JAAAAAElFTkSuQmCC" michael@0: } michael@0: }; michael@0: michael@0: // The process of adding a new default snippet involves: michael@0: // * add a new entity to aboutHome.dtd michael@0: // * add a for it in aboutHome.xhtml michael@0: // * add an entry here in the proper ordering (based on spans) michael@0: // The part of the snippet will be linked to the corresponding url. michael@0: const DEFAULT_SNIPPETS_URLS = [ michael@0: "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet" michael@0: , "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons" michael@0: ]; michael@0: michael@0: const SNIPPETS_UPDATE_INTERVAL_MS = 86400000; // 1 Day. michael@0: michael@0: // IndexedDB storage constants. michael@0: const DATABASE_NAME = "abouthome"; michael@0: const DATABASE_VERSION = 1; michael@0: const SNIPPETS_OBJECTSTORE_NAME = "snippets"; michael@0: michael@0: // This global tracks if the page has been set up before, to prevent double inits michael@0: let gInitialized = false; michael@0: let gObserver = new MutationObserver(function (mutations) { michael@0: for (let mutation of mutations) { michael@0: if (mutation.attributeName == "searchEngineName") { michael@0: setupSearchEngine(); michael@0: if (!gInitialized) { michael@0: ensureSnippetsMapThen(loadSnippets); michael@0: gInitialized = true; michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: }); michael@0: michael@0: window.addEventListener("pageshow", function () { michael@0: // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs michael@0: // later and may use asynchronous getters. michael@0: window.gObserver.observe(document.documentElement, { attributes: true }); michael@0: fitToWidth(); michael@0: window.addEventListener("resize", fitToWidth); michael@0: michael@0: // Ask chrome to update snippets. michael@0: var event = new CustomEvent("AboutHomeLoad", {bubbles:true}); michael@0: document.dispatchEvent(event); michael@0: }); michael@0: michael@0: window.addEventListener("pagehide", function() { michael@0: window.gObserver.disconnect(); michael@0: window.removeEventListener("resize", fitToWidth); michael@0: }); michael@0: michael@0: // This object has the same interface as Map and is used to store and retrieve michael@0: // the snippets data. It is lazily initialized by ensureSnippetsMapThen(), so michael@0: // be sure its callback returned before trying to use it. michael@0: let gSnippetsMap; michael@0: let gSnippetsMapCallbacks = []; michael@0: michael@0: /** michael@0: * Ensure the snippets map is properly initialized. michael@0: * michael@0: * @param aCallback michael@0: * Invoked once the map has been initialized, gets the map as argument. michael@0: * @note Snippets should never directly manage the underlying storage, since michael@0: * it may change inadvertently. michael@0: */ michael@0: function ensureSnippetsMapThen(aCallback) michael@0: { michael@0: if (gSnippetsMap) { michael@0: aCallback(gSnippetsMap); michael@0: return; michael@0: } michael@0: michael@0: // Handle multiple requests during the async initialization. michael@0: gSnippetsMapCallbacks.push(aCallback); michael@0: if (gSnippetsMapCallbacks.length > 1) { michael@0: // We are already updating, the callbacks will be invoked when done. michael@0: return; michael@0: } michael@0: michael@0: let invokeCallbacks = function () { michael@0: if (!gSnippetsMap) { michael@0: gSnippetsMap = Object.freeze(new Map()); michael@0: } michael@0: michael@0: for (let callback of gSnippetsMapCallbacks) { michael@0: callback(gSnippetsMap); michael@0: } michael@0: gSnippetsMapCallbacks.length = 0; michael@0: } michael@0: michael@0: let openRequest = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); michael@0: michael@0: openRequest.onerror = function (event) { michael@0: // Try to delete the old database so that we can start this process over michael@0: // next time. michael@0: indexedDB.deleteDatabase(DATABASE_NAME); michael@0: invokeCallbacks(); michael@0: }; michael@0: michael@0: openRequest.onupgradeneeded = function (event) { michael@0: let db = event.target.result; michael@0: if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) { michael@0: db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME); michael@0: } michael@0: } michael@0: michael@0: openRequest.onsuccess = function (event) { michael@0: let db = event.target.result; michael@0: michael@0: db.onerror = function (event) { michael@0: invokeCallbacks(); michael@0: } michael@0: michael@0: db.onversionchange = function (event) { michael@0: event.target.close(); michael@0: invokeCallbacks(); michael@0: } michael@0: michael@0: let cache = new Map(); michael@0: let cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME) michael@0: .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor(); michael@0: cursorRequest.onerror = function (event) { michael@0: invokeCallbacks(); michael@0: } michael@0: michael@0: cursorRequest.onsuccess = function(event) { michael@0: let cursor = event.target.result; michael@0: michael@0: // Populate the cache from the persistent storage. michael@0: if (cursor) { michael@0: cache.set(cursor.key, cursor.value); michael@0: cursor.continue(); michael@0: return; michael@0: } michael@0: michael@0: // The cache has been filled up, create the snippets map. michael@0: gSnippetsMap = Object.freeze({ michael@0: get: function (aKey) cache.get(aKey), michael@0: set: function (aKey, aValue) { michael@0: db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") michael@0: .objectStore(SNIPPETS_OBJECTSTORE_NAME).put(aValue, aKey); michael@0: return cache.set(aKey, aValue); michael@0: }, michael@0: has: function (aKey) cache.has(aKey), michael@0: delete: function (aKey) { michael@0: db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") michael@0: .objectStore(SNIPPETS_OBJECTSTORE_NAME).delete(aKey); michael@0: return cache.delete(aKey); michael@0: }, michael@0: clear: function () { michael@0: db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") michael@0: .objectStore(SNIPPETS_OBJECTSTORE_NAME).clear(); michael@0: return cache.clear(); michael@0: }, michael@0: get size() cache.size michael@0: }); michael@0: michael@0: setTimeout(invokeCallbacks, 0); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function onSearchSubmit(aEvent) michael@0: { michael@0: let searchTerms = document.getElementById("searchText").value; michael@0: let engineName = document.documentElement.getAttribute("searchEngineName"); michael@0: michael@0: if (engineName && searchTerms.length > 0) { michael@0: // Send an event that will perform a search and Firefox Health Report will michael@0: // record that a search from about:home has occurred. michael@0: let eventData = JSON.stringify({ michael@0: engineName: engineName, michael@0: searchTerms: searchTerms michael@0: }); michael@0: let event = new CustomEvent("AboutHomeSearchEvent", {detail: eventData}); michael@0: document.dispatchEvent(event); michael@0: } michael@0: michael@0: aEvent.preventDefault(); michael@0: } michael@0: michael@0: michael@0: function setupSearchEngine() michael@0: { michael@0: // The "autofocus" attribute doesn't focus the form element michael@0: // immediately when the element is first drawn, so the michael@0: // attribute is also used for styling when the page first loads. michael@0: let searchText = document.getElementById("searchText"); michael@0: searchText.addEventListener("blur", function searchText_onBlur() { michael@0: searchText.removeEventListener("blur", searchText_onBlur); michael@0: searchText.removeAttribute("autofocus"); michael@0: }); michael@0: michael@0: let searchEngineName = document.documentElement.getAttribute("searchEngineName"); michael@0: let searchEngineInfo = SEARCH_ENGINES[searchEngineName]; michael@0: let logoElt = document.getElementById("searchEngineLogo"); michael@0: michael@0: // Add search engine logo. michael@0: if (searchEngineInfo && searchEngineInfo.image) { michael@0: logoElt.parentNode.hidden = false; michael@0: logoElt.src = searchEngineInfo.image; michael@0: logoElt.alt = searchEngineName; michael@0: searchText.placeholder = ""; michael@0: } michael@0: else { michael@0: logoElt.parentNode.hidden = true; michael@0: searchText.placeholder = searchEngineName; michael@0: } michael@0: michael@0: } michael@0: michael@0: /** michael@0: * Inform the test harness that we're done loading the page. michael@0: */ michael@0: function loadSucceeded() michael@0: { michael@0: var event = new CustomEvent("AboutHomeLoadSnippetsSucceeded", {bubbles:true}); michael@0: document.dispatchEvent(event); michael@0: } michael@0: michael@0: /** michael@0: * Update the local snippets from the remote storage, then show them through michael@0: * showSnippets. michael@0: */ michael@0: function loadSnippets() michael@0: { michael@0: if (!gSnippetsMap) michael@0: throw new Error("Snippets map has not properly been initialized"); michael@0: michael@0: // Allow tests to modify the snippets map before using it. michael@0: var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true}); michael@0: document.dispatchEvent(event); michael@0: michael@0: // Check cached snippets version. michael@0: let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0; michael@0: let currentVersion = document.documentElement.getAttribute("snippetsVersion"); michael@0: if (cachedVersion < currentVersion) { michael@0: // The cached snippets are old and unsupported, restart from scratch. michael@0: gSnippetsMap.clear(); michael@0: } michael@0: michael@0: // Check last snippets update. michael@0: let lastUpdate = gSnippetsMap.get("snippets-last-update"); michael@0: let updateURL = document.documentElement.getAttribute("snippetsURL"); michael@0: let shouldUpdate = !lastUpdate || michael@0: Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS; michael@0: if (updateURL && shouldUpdate) { michael@0: // Try to update from network. michael@0: let xhr = new XMLHttpRequest(); michael@0: try { michael@0: xhr.open("GET", updateURL, true); michael@0: } catch (ex) { michael@0: showSnippets(); michael@0: loadSucceeded(); michael@0: return; michael@0: } michael@0: // Even if fetching should fail we don't want to spam the server, thus michael@0: // set the last update time regardless its results. Will retry tomorrow. michael@0: gSnippetsMap.set("snippets-last-update", Date.now()); michael@0: xhr.onerror = function (event) { michael@0: showSnippets(); michael@0: }; michael@0: xhr.onload = function (event) michael@0: { michael@0: if (xhr.status == 200) { michael@0: gSnippetsMap.set("snippets", xhr.responseText); michael@0: gSnippetsMap.set("snippets-cached-version", currentVersion); michael@0: } michael@0: showSnippets(); michael@0: loadSucceeded(); michael@0: }; michael@0: xhr.send(null); michael@0: } else { michael@0: showSnippets(); michael@0: loadSucceeded(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Shows locally cached remote snippets, or default ones when not available. michael@0: * michael@0: * @note: snippets should never invoke showSnippets(), or they may cause michael@0: * a "too much recursion" exception. michael@0: */ michael@0: let _snippetsShown = false; michael@0: function showSnippets() michael@0: { michael@0: let snippetsElt = document.getElementById("snippets"); michael@0: michael@0: // Show about:rights notification, if needed. michael@0: let showRights = document.documentElement.getAttribute("showKnowYourRights"); michael@0: if (showRights) { michael@0: let rightsElt = document.getElementById("rightsSnippet"); michael@0: let anchor = rightsElt.getElementsByTagName("a")[0]; michael@0: anchor.href = "about:rights"; michael@0: snippetsElt.appendChild(rightsElt); michael@0: rightsElt.removeAttribute("hidden"); michael@0: return; michael@0: } michael@0: michael@0: if (!gSnippetsMap) michael@0: throw new Error("Snippets map has not properly been initialized"); michael@0: if (_snippetsShown) { michael@0: // There's something wrong with the remote snippets, just in case fall back michael@0: // to the default snippets. michael@0: showDefaultSnippets(); michael@0: throw new Error("showSnippets should never be invoked multiple times"); michael@0: } michael@0: _snippetsShown = true; michael@0: michael@0: let snippets = gSnippetsMap.get("snippets"); michael@0: // If there are remotely fetched snippets, try to to show them. michael@0: if (snippets) { michael@0: // Injecting snippets can throw if they're invalid XML. michael@0: try { michael@0: snippetsElt.innerHTML = snippets; michael@0: // Scripts injected by innerHTML are inactive, so we have to relocate them michael@0: // through DOM manipulation to activate their contents. michael@0: Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) { michael@0: let relocatedScript = document.createElement("script"); michael@0: relocatedScript.type = "text/javascript;version=1.8"; michael@0: relocatedScript.text = elt.text; michael@0: elt.parentNode.replaceChild(relocatedScript, elt); michael@0: }); michael@0: return; michael@0: } catch (ex) { michael@0: // Bad content, continue to show default snippets. michael@0: } michael@0: } michael@0: michael@0: showDefaultSnippets(); michael@0: } michael@0: michael@0: /** michael@0: * Clear snippets element contents and show default snippets. michael@0: */ michael@0: function showDefaultSnippets() michael@0: { michael@0: // Clear eventual contents... michael@0: let snippetsElt = document.getElementById("snippets"); michael@0: snippetsElt.innerHTML = ""; michael@0: michael@0: // ...then show default snippets. michael@0: let defaultSnippetsElt = document.getElementById("defaultSnippets"); michael@0: let entries = defaultSnippetsElt.querySelectorAll("span"); michael@0: // Choose a random snippet. Assume there is always at least one. michael@0: let randIndex = Math.floor(Math.random() * entries.length); michael@0: let entry = entries[randIndex]; michael@0: // Inject url in the eventual link. michael@0: if (DEFAULT_SNIPPETS_URLS[randIndex]) { michael@0: let links = entry.getElementsByTagName("a"); michael@0: // Default snippets can have only one link, otherwise something is messed michael@0: // up in the translation. michael@0: if (links.length == 1) { michael@0: links[0].href = DEFAULT_SNIPPETS_URLS[randIndex]; michael@0: } michael@0: } michael@0: // Move the default snippet to the snippets element. michael@0: snippetsElt.appendChild(entry); michael@0: } michael@0: michael@0: function fitToWidth() { michael@0: if (window.scrollMaxX) { michael@0: document.body.setAttribute("narrow", "true"); michael@0: } else if (document.body.hasAttribute("narrow")) { michael@0: document.body.removeAttribute("narrow"); michael@0: fitToWidth(); michael@0: } michael@0: }