|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 const SEARCH_ENGINES = { |
|
6 "Google": { |
|
7 // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.; |
|
8 // it will be scaled down as necessary on lower-dpi displays. |
|
9 // This needs to be defined in a single line to keep the JS parser from creating many |
|
10 // intermediate strings in memory. See bug 986672. |
|
11 image: "data:image/png;base64,\ |
|
12 iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ\ |
|
13 bWFnZVJlYWR5ccllPAAAGrFJREFUeNrtfHt4VdW172+utZOASLJ5+BaIFrUeXkFsa0Fl++gDnznV\ |
|
14 VlvFxt7aqvUUarXtse3Bau35ak/rZ9XT26NtfOvV6wFET+FYCQEKWqsQIT5RCAgSXnlnrzXneNw/\ |
|
15 1lphJSSQ8BB7bub3zW+LO3uN+fiNMcf4jTEX0N/6W3/rb/2tv30smtnXB3zmRi2FQakxQNKX3WkW\ |
|
16 9S/tgW3HLpmQM543A0BWVSHMYGIwOTDxzxrOf3/RQQfMZ2/SLAvKhTFVBGUqKFONH2QAzwOMF38a\ |
|
17 wHhYZAxWAqhe/iszp3+b970d/sInc57vz/J8L2eMB2MAEYkBQ6DQ3dRw4dq7AUjcP3rAfPZmLWXC\ |
|
18 LHKoIAcQAUxaB5EaEfc6AEBhjDEwmcx43/fO9HxT4vkReBIAAZgjgodW3NcPnn1sHgD/iHknn+0d\ |
|
19 6s8XEUhsXXac/34WAAGw8afuT8GZ3X055YeSJcIsG+pMZwFn0UihezRofPt3G54f/0E8cNMN+Myo\ |
|
20 8jVTCgYd823PLzrPeIBnABiUQ1F+UoWsVOYb33mkoKp/7/dKyT0AGc47X4s0sjBEoLxbBqAQAMfW\ |
|
21 Rfe38B4BM+VHUkYOs8mi1FrABbK4dcvK73zwp1M3xYPOxANKBqbpCdXNGb0UwPKRF74xpfDQ0t+K\ |
|
22 54+IvlKoahmAhaO/mv/ZmicG3tqPgT61ZM2dZMQJOYhIdByRM/F3dCCOox4Bc3oEliqyyNoQCPPu\ |
|
23 sXceKZqRsigu7pwaWBowiRb46+f9Q1V2wl1nDx09/R7jF30x9adNlN8yPx4DHwht+B/cBIBoRqeI\ |
|
24 E4hE/oshTcB0wNbT6/o/zrhFyohR5ZxmrVWE+fDxdx4puhGAH4OkPe5B6pykeJAc/7cDEMZ/095Y\ |
|
25 870P339m+BXs2v4kbCFsm9u2vnpJ3bzR7wAo2B/R2v+PjSnyXcRxtOLUSXFxwAFz5i2SZUIVO82S\ |
|
26 BWye/vLOIwNvjL8OYqCEfXCmJAZPHkC7sK1REbj2+lmbq86qTVmmfuuyN2cTiREWKCvACgml9kDL\ |
|
27 7HQksehsZmSdA6yVpsa6P38v3swg7m4vN1dGXrThKGP8yS5fP33j/LEvxKDbl2f2A0YFCtkZQDOa\ |
|
28 PjLAnP4jrmBGjh1AVhG2ttxfX33++vjY2eeNXf/siLUAzgEwMJZrY2vF/Vu/t4BRqCqgCmj07wMV\ |
|
29 HXUCzJQfUlZE72ICnANcqNj21h8eiK1AX46gXh29KT9H+rd9XxBjYGCgig7QHOgjPgMAKigXQZYp\ |
|
30 si4uCOc3v35zY2wF9ufGSgxA7fdd9g8ho9ol4P4ojiQWnSUMMANECrJNy1NWYH8eGfsEvJbLv1IK\ |
|
31 1XIAUwEtA0xplJMwjcaYlTDeShg8dOgjj6/cJxNYfWIWkHJoh5yyjkSZ8RbB89YBZq4/pXafGeuz\ |
|
32 b9WciXJxo2B2houqgAjABJCLOwFMqFv57+bBxMIAJm1det3avnl1OYCLAeSgWhofaY1QXQSRuYc+\ |
|
33 /OiD3QLmUzNdqTBKhRVMADsF5beuToXJB90KtFz+lVIVniXOVUAUqjpXVB4WwPjGTPB8/0zjeTnj\ |
|
34 ezl43szmKy6vNkDF4MeeXNc3oJyUhfAMkJsJkSxUVrLos6o6z/O8Ucb3phrPzyHKeVTwkpPXseg3\ |
|
35 Cqe+1SfG+swfaw6KGTAoJ5eyGF3IBeEIJB2AcXxb0FI/L45uFQBMGiu6Z3ai9eqrclBUClFWVatV\ |
|
36 5GERNT5wEVQnQLUcIuVNX75kFjn60rA5c1d0AoywlkcxfdwZ2LSgbOmBZAv70povu7RcyFUqcZYd\ |
|
37 Pbxix44fnLv8pbYUOWh+P3ZM9uJRo34xoLDgq8b3YTxvqhqsaPzyJTdmn36msjdyqPqkMhWqBFGZ\ |
|
38 MtV8uDX4zMjp2zemyEoPgGn4zyOvGzy48A54GcD3Sz1jFrqqE+4uOOvdmb0ASlYEs5mQE9afUdhy\ |
|
39 0yv3lHzwya/8ZcjgI0+5yssU3QKYkgQ4Ivp60LL1n8kBQfOWuvdnj6uLldgHQKoKxU7HV/eg2y1X\ |
|
40 XXmXEs1U0ZVb29o//4k5c5P5eQB+s+68aVeUFBTcCxUoS6kRWfjhueecc9SfX3ytA9QTr7eVACqY\ |
|
41 FDYEwnbB2qcHHg6gLY6ODhpomi77coUyVaojhKH9+ZHzF/wqXiztEg34APxNX/jCvQOLCi83fpy8\ |
|
42 UsCJXHLYnGdn785S0uKTyyBUBXJZcW5x4bSN56ciyLQcD4Bf/+ThVwwbUvRb+JkoswqAWX5b9Lm1\ |
|
43 M3uSM/UnUiaCKiZk2blvvnxX0ePxuBNAmpMur51wyLBPzjVeBBoVwIXBk6vuP+SG+LkcuwkWAA96\ |
|
44 /JjZKnKxkACkkFb5Nztz220xX9bJlWi+6opKFalQlpqlmzZNu6B6SaJ0knKJ/DW5qd8p8TO3x6AB\ |
|
45 qza1EE06cdmy9wDAY5LjmBTMkQnUnZ42H0ywNF52aU6FK4UY5NySI+cv+E3MCnMM5HyqtwFoO3rB\ |
|
46 gmuDMFjGjiCOIEQwzH9c+7lzju+JTaYlJ2ehUqXMWWFqeurFxqsAFMVf25Ss9kTOEZdvebClJbxT\ |
|
47 yUGZoEzwlL/b9tzRX+pOztSfSBZApSqyIrL45buKnkaUJEzLCN5+csxr+ab6fyILkI2OIZYBlx9/\ |
|
48 2bYvpLgw2+EqKLKdwoceVKJp+tfuEpYKZcaW1tZbLqheEsbj3GV+oxdV3x0GwQZrHUIiWKIST3Vm\ |
|
49 DG54zFrKrBBWiGgSyx9Uv6Xh0n/MKlGlOII4h80trQ+kuJt8HGklZHg6FZF/Y/uOb7O1YOvAzkGt\ |
|
50 Kxmoehe6SYNEpkErwZIFC4I2fuLKf2tLtDOPzumPhA6wAPJDLt1yuzjaAEcAMUCMApXfvPP7IcO6\ |
|
51 gkYFs4RRpgy49qanUsAPu/T8W48e/YwL6S/kYtBYwM8U/yu6KVlQUShr9CkKyK7b1vDVy0qVeaYy\ |
|
52 gaxbdeK85/8a/z7sYR3zgXM1gXUInEPoCEw8PR6z8YQxaidQPh6RrgrPEOZS4chKjFuydEEKFD1x\ |
|
53 QgrAnfO3V98Jw/B5dhFgmByU+MK/nnrq6K6gcQtPyqlIubJAibCxPv/fsVVNgCI9yGEAQdBq71NH\ |
|
54 UEdQIoBo5PBBeklazuQfSpYFM0UAFsDmd2yMf9+1XkUT3otc8AiRwpFChCBCI0detGbSLtYr5uw6\ |
|
55 tk26XctZwgxhRt65ZSmr1t389M1Jk85wzKcHRAiJkCfasDnI/0sMGN+jlLMrAigMhp0+f+TBBIw4\ |
|
56 milEYOcQBHZZAoZeEIgKgIIgeJbD2MqEFhxaDAFmdAWMisxQFigzlAUnX9e4rA9yeHuTna3koBQB\ |
|
57 RogxwOPvxNbQAAA7VHQEFKSQKEFIu4lA5d3HiiuFNB4XQZlhUHBK11QO0oRdD7ouROVCkeJZG7ak\ |
|
58 /KBOYHlz4sTy1WVlVY5oYego2+bs82+3tFw6YcVrp01dteqpxNfyhKQuGlxCMSsKBh570ABT/8XP\ |
|
59 5dhRVpyDWAd2Ns0O9yrhWdfcMpvCEByEoNCCwhBgvgBdM+PM5TH5FPW+1ZLo8de2viehe12dhVoH\ |
|
60 OAtDPO61O4o+kYCTnE5wVuGsxlzKHul7BUDKdomKgwpB2QHAyNiP2Dl+0Z2WRXZ9YP0F55WJczvX\ |
|
61 0jp09U3fLiurWD1+/NqQaHZIVNbu3O1vt7aM+fSqVRWXvPvu0pRldwAkQ5brjO+NMh0kgMIvGjYZ\ |
|
62 wIKETPxIrYt1U5M8iThKJil9yZGc++ab298dP36Jb8wZohqhQHRErKEeAA6fG5FT5yIlYYI6tzfO\ |
|
63 vtiQni3MYDw0ChqEgUMyejyAdwGwDeW4ZI9FAGQOmwzgv/cERmZbDXhnKBNUGMJkUhGVduSSJJ1P\ |
|
64 6rw8HIalJo7ilBkchgCgL48fVzLceDc4kZnWUdap1AQi10x+660n4jXyk1M7ZXEZgHhMUkMO4Njp\ |
|
65 hQGMf8h56Fx++ZE1a+1xZC2Szjs3sk9uUEhUbSMvP3LeyOGZ0tKJiearo1J1DHVRPYmS7JUcG2g1\ |
|
66 pxxUsooBnpmQWAOb10YbKGygcKFCZOC0XqxrRKokCBQG5euX77In2k1P+2hhWEZBAAoCuCCEcW7E\ |
|
67 2xMn/m6oYo0jyjnmuc3Off6UN96YMvmtt5LILSmQ61r3xAA0I+xqPBiIejAd1f7e2MPPfvm4LQs/\ |
|
68 89a+bP6nZuSzfsaU+T7g+UBixYQVRFGS01kFO22srRy0EgA4CEvFRHS3MANMY/fGbybmlQqAFSBV\ |
|
69 sCp8kWwCGA5dqefFShnnRV77ecHYU37iXuqLoB0tsuIo34v3NfJR1GlJsrnOuiXGy1y8k+rwxh57\ |
|
70 3srSD/6rbLdra7yMqgjUCGAULR8uWr0LJPYAGApCeCbKNygLPKIxJ65YOSU+YpLUUCYGiqBzQVy3\ |
|
71 Ft1zbevnJl60UARqACgcVDo9ZZr63Mqua68QxlpmrWJC1FmrmLSKCFVktcpZrbKhzg4D26E5Lgjg\ |
|
72 8vnoMwwh1hU/dvTRo/qcDyJqcESw5Dp6o3XNHVrqLDSubAdFjuXwwWZcX+Wc9APboKxQUoiLurXa\ |
|
73 IYfCpjlCDsoxZ6OCouLRt+xpbY3nA8aDMR6E2+9vffOWxl02cQ+Bbdjevt7l83D5ABRaKNHYO484\ |
|
74 YmgMkoJ4jElCOL8Lz9NN87YumrRDxc2DElQZKgIVhZcZcO1hZ74wtK/H0thvtuXGXdM2S0S/ziQ1\ |
|
75 FPJiG7pHwvbgDhtKnQ0VNhCEeUHQLmiuf2fymieGvJGY8DCfX+yCEC5xWIlwtO+P6+s4VESJGS4+\ |
|
76 liwxKjZ/2FGRZvPhYgktxEZdHWOAr2P34ihWIQWTgJ2CnWJbo9Ymz1g/5+h1QsF9wgKJ19Z4hV87\ |
|
77 4fKNE3cnx8v4V8H4UOjqhvce+zW6qdWVlOvSjQsDlw/WUT4A5QNQGIJDizMPHXR+CiRBb4GSzlYr\ |
|
78 26Z7vYKSC42nUOPBqA9VU1I0ZOJPEYWj1NvVW/3AoEUAFgO4IzZ1hYk2jf9WUw7IjCIXHUVhXrFp\ |
|
79 /sQtKZPIoXXr/PjoSkZeoHo6gP/bFyeciECqcHG3IrXp37a2SF3xQNPxRAXgq5nS1bHsDWCYALYA\ |
|
80 u+h0W/impI8Pad9ec/vAoWVTjV84Nsn5FAwcvmDMN5rOqf1jyatdHzjuGjvThloKYH3b5qVXt775\ |
|
81 44ZuN1QEKknF3a6ImfDee4tWjBrV6R5Qoeq1AP6Avaxx8gDolhdPXAh2qzQmZFQ4ZhALrj/mvLpT\ |
|
82 +qhxya0BP5VVZQBkA6jNR0AJ2xUUcjKGjsx4k3PVYUwaJU6rJ3reLiHlHppjBjF3fLYSzU/noEZ8\ |
|
83 3611VusoVJBVsFWAdezim/3jemSFe+SNIsvCpAhCXf7TBZI+PnTr4nO2t2xcME3ZroYKIouEEqDo\ |
|
84 xfHfav/GxOttFgBOucGWll0XVqrqXYDWNLz3aG7bsovWp4i2TvkhScLqNBezq/M/zxLBxV2Yx/75\ |
|
85 yCPP6usc04CJ+B3bcLMwQTiK+0UIwgz1ip8+4pyaYX0x0SnWMkjnYGygkm9nBO0MGzoI2TTDyQBw\ |
|
86 7ubNawPmeZYZNt5wZhrxX8OHX9yXSTJzGcVgIWasbs8/hc7XRzXM670cg0Vs5H+MHm6u74ucrb/K\ |
|
87 lAlFPoySoqFFn+rm+OCGV762df2cYWe4fP0M5qDWhoowRIm1/h+s1YZx3wrVOV1LDhXMaGzfXntF\ |
|
88 46vXtMQRS/clsqRRT9SNd0GMBo6edRStZbKeg4D//ciQIcP2CTDbqsdVKQePq1JMFkXxv4qO9AaM\ |
|
89 fPGoaeuG9kXp0LkU0wGgMFC1gYAdAeyg0m3IrE3W3mtTvodjRpHq9X3xL4h5Qsq63P/z9ra6LqSc\ |
|
90 vvmBPkwOTex2lnf4wNee/47fa99NGGVJ8Zl1qP3UPfwkdr15mDDV+Y3Pf+Kh9c9kz9pee89J7dve\ |
|
91 vaRt+7qLbVv47y5UUKggp3BB/okNz0/aHI8332OaIgELxWDpptQtt6X+Qcu03nVYGQYxjxzl+7/e\ |
|
92 GyvjdYrCtv31JiW7QTjy6qWj83jF4AeP/MLaodiHRtZBXAihEEIWkq4eSgGmvKGhqpX5d1YEVhiW\ |
|
93 BaI6Zf6QITN7s5ELhw4tZZavkwhIZMOC1rZfo5s64nPv4+1NzXot2/hYiqKckglH4/7eRojCOosp\ |
|
94 St6u2ijfS1Hv3I0SdVy5aam9ecumBeOqN8w7aRkxSlMVdRDmRHa4m5xWPKPEusUA6maIrcy/cCKw\ |
|
95 InASKaCoXrlo2LAH+xpMpAEjLauu2ObaNnxVmZqUHaI8SaR+KnIhTPHCo6ZtOn6vk4qUPNNGnV2P\ |
|
96 J0ptENweMq92zHBMcMwwIrfMLS6etKdJEnMlCYOZm9YE4dUPkWvsIUckJ/+SZwd5PCEOEBc5rh7j\ |
|
97 grqf+VfvSc7mO/xZSihVAra3YMY/PqqrUhZVe7C8yRHTBqAVQJuQN5idgJ2ASQAz4PJjptWevKc0\ |
|
98 RZQ0TQATRWDd/dmFDQ2VeaLH0z4dRVTK9EXZ7IqFJSXH7W6eLw0blntp2NAydGOSqPGVs/5mW9Zc\ |
|
99 JGKbRSxELIRDCFuIuAmiBa8eMW37rcdc1JDtM+3PYdSp43k9/ulPgmDrsnz+vFBktRWBZYEVKSlU\ |
|
100 feH5wYPP7u5Hfy4uzi4oLq50IjkSaXrf2vIfBPnV6PlKiwKg0XfyNe2BPkmJ8+oUGeh/bLjNu7En\ |
|
101 0Gy+w5sppLcyKRra9IZJ98hTvciop9MPSSFUwGTnEjHICsgpyKHYHzjquWMvrJ+wewUENPFjCIAx\ |
|
102 k3uStyIMbw5FVieWJvJpBE5kgqq+X1VcPGdRcfHMxSUluSUlJbmlUZ+1tKRkLRGVnrZ9Rw12rSLt\ |
|
103 sDpFg8vmfbpw0HH3wcuMMSaiao2XAbwMjPFhPL/ReN6DfsY8tHHekN0WXR929vqsCpWruFshPEqF\ |
|
104 o3IyADuWTxgea1rYTbRVeEMmc+SnCwp+OcB4l3kmLq0D4BnzkA/MMUBjvDMXC1DBqlkCFr9N9E//\ |
|
105 HIZpPyDsQVuTFwsMfP273k8GFeLbvo9izwe8DGA8VMPgIc/D2piALlPFDGWUMqNuazOun/RbeQU7\ |
|
106 L/zl0cfC+SPOXjG84NBRawCvJNoSE7PiBgr5Xx/MKf7jLnzIbUPKlHVF5C11KgJfD9+shY8Vxjd3\ |
|
107 0780rEvP8bFDDvnVQGO+lU5MeTDwzM5aTbOzNyrw/XNbWx9JFLknk+sjqjobUHJq9XS/cNj3jZcZ\ |
|
108 Ac9PwBIDyAeMD2O8RhhvpTFYqYpGqMQOM2UhlFOhsvjfgNJ6ofxyoZaXbHPt8mDNjDU9ACYBbyGA\ |
|
109 AT/KZEZ/MpO5qciYyRlgROeJGSh0nQCL21Ufmx4EL8dMpqScRt4DFVAAYMCtORx+0Rhz7aFF+GJB\ |
|
110 BmNM/JKklGo1KlBtHZ474U79P9hZOZcQYb0unD/mwu05qADCZwE4C8Y7I3kTk4kFx+mUuzfMKf5e\ |
|
111 +rn+rUMq4PR4hFII0gw0xpdvGAWGoDqHf9m8IuV8m2Qtf1pQMPok37+50JhpHlC8EzwRcAzwOqs+\ |
|
112 Vkv06I+da04nInd3RvuxgCIAhcUTF5zvFQ79oucP+Cy8zIjE6qQnt5Pviu5IqAogVKNCNSrBUte6\ |
|
113 blnrqi/Vo3O9rI3Pc7cbP6sgGQcAf7rvl3zK908uBKjAGK5jrrmNKKHj/RS3E6L3V2USLUzkZAB4\ |
|
114 i75pTivwwQMyoKYQ685+QOtScvzUHPbIlJ54ZVsuDPTrZDmnQqUQggo1qkoNRDyFeJ6XGQfjF0fW\ |
|
115 3O9YWxW6adNzw36Dzm/JKEJ0k7QgtfiSygd1vSrkdZ3jlb6fneT7Y+MN1xrmVX9gbkw9q1MdsemF\ |
|
116 U5wkpwqSRSw49gfZAcPPHOsVlIww/sBjjPEVnqfGZEQlWKVCjWK31TW/dv56pCruU126TGxPl+US\ |
|
117 IrAgNQ7TQ+pNukQqfalLNimApvMt6CZMTvsiu3VOJ17XnrNWZ9m85oK8Qmz4sFB+CeXrF29dfOqG\ |
|
118 1PwKs6fOKyvKjrnb8wrHGD8TWfCOEoX85zb96dgXY9leN2NM+y3SJZG4u7XsSldIykFPz09NHxbR\ |
|
119 T2U3M11AsKf8aRqtnBqQoG91oWkGOS0/XaQo2Pf3u5mUDK9LukD7Mv5Tv9teSQ4VzipsINUtW9Zc\ |
|
120 t/mFiRu7WbcOuQNP+MXQ4hGX3mEKBl1mjB9bbwAqSz6cf+TZ8Qaabta/u6hM92ItpZs5dvyor5R/\ |
|
121 dwvp9QAa6eFzfxRlpVMk2mXh93czeyPn1Bn5ShWtYAJsyEve+OPgC7Hzmgx3USDtejQedlbtDX7h\ |
|
122 0Ns6HChV5LcvP7rpb1+qx/690dHrtewL05c2c7ZLtrM91fOpDGjXyvT9+WYBPQAg3NPcey1n4vVt\ |
|
123 FUJSIfGNjJZNy2ekkqzpazIJOefSoTaA9q1VY+5Wbvs9NAoYVBkFh5Sesi9lJ/u6lt5+WETpoi2M\ |
|
124 PpZU/k9szmKGtVGRWBjQ6g3zP78pxfSGKb+tJ4LPAsi31S/+uXCUlVZmCIc+DlI15L4Cpr/1FA1d\ |
|
125 0VLqAilzgcCGChdQc5eoTXqpkNS66hv1YLsUElURiG1sOZj7lunf3v3fwlBKjRfX9EjEHKcscV98\ |
|
126 D40zRKIqgEpz4yvTVnfjU/VbmL/r4yhwTTbPCNsZNi8g50/OnvbCsXu5wQqVURCBuOb7seu98n7A\ |
|
127 /L23Tc8NX8mW6pL73UoOhYPH/GJv/I7Dzlqbg5pRUG1q++A//+Ng+4f9gDlATVzLHfErZiHioKrn\ |
|
128 H37uhgeG597sdYnIYeeszypQqQawre9dHNbd0Yj9/5KnfsB8DJpuXXj8Q+ryj3dUZglD1Uz3MsWv\ |
|
129 HX7uh1fv6QGHn7upAmrWQpEV2zSt+bVptamw+6C9VaP/hcoHrvkABgydUjPLywy6Oboh6HW6PgLj\ |
|
130 LYqStqYRQHKDMQflMhXOQrnata27tvGvufrEn8ZBfmdPP2AO7NpmAAw85B8qTyjKlt1svAHTjPGL\ |
|
131 k4w0jAcTAyllnBoh9Kxw/tEdS8cuT0WyH4vX1PYD5qMBzQDE2eFDxz09zsscWuwVHX6a8YwaFAiM\ |
|
132 NAkHr4vdUdf82rQN6JwnSl4N4vAxeKdxP2A+mjXuKTvcXcY9TdOnyxPk4zKZ/vbRAqe75C3QfZZY\ |
|
133 0P/y6/7299z+H4QrdGsoib8JAAAAAElFTkSuQmCC" |
|
134 } |
|
135 }; |
|
136 |
|
137 // The process of adding a new default snippet involves: |
|
138 // * add a new entity to aboutHome.dtd |
|
139 // * add a <span/> for it in aboutHome.xhtml |
|
140 // * add an entry here in the proper ordering (based on spans) |
|
141 // The <a/> part of the snippet will be linked to the corresponding url. |
|
142 const DEFAULT_SNIPPETS_URLS = [ |
|
143 "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet" |
|
144 , "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons" |
|
145 ]; |
|
146 |
|
147 const SNIPPETS_UPDATE_INTERVAL_MS = 86400000; // 1 Day. |
|
148 |
|
149 // IndexedDB storage constants. |
|
150 const DATABASE_NAME = "abouthome"; |
|
151 const DATABASE_VERSION = 1; |
|
152 const SNIPPETS_OBJECTSTORE_NAME = "snippets"; |
|
153 |
|
154 // This global tracks if the page has been set up before, to prevent double inits |
|
155 let gInitialized = false; |
|
156 let gObserver = new MutationObserver(function (mutations) { |
|
157 for (let mutation of mutations) { |
|
158 if (mutation.attributeName == "searchEngineName") { |
|
159 setupSearchEngine(); |
|
160 if (!gInitialized) { |
|
161 ensureSnippetsMapThen(loadSnippets); |
|
162 gInitialized = true; |
|
163 } |
|
164 return; |
|
165 } |
|
166 } |
|
167 }); |
|
168 |
|
169 window.addEventListener("pageshow", function () { |
|
170 // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs |
|
171 // later and may use asynchronous getters. |
|
172 window.gObserver.observe(document.documentElement, { attributes: true }); |
|
173 fitToWidth(); |
|
174 window.addEventListener("resize", fitToWidth); |
|
175 |
|
176 // Ask chrome to update snippets. |
|
177 var event = new CustomEvent("AboutHomeLoad", {bubbles:true}); |
|
178 document.dispatchEvent(event); |
|
179 }); |
|
180 |
|
181 window.addEventListener("pagehide", function() { |
|
182 window.gObserver.disconnect(); |
|
183 window.removeEventListener("resize", fitToWidth); |
|
184 }); |
|
185 |
|
186 // This object has the same interface as Map and is used to store and retrieve |
|
187 // the snippets data. It is lazily initialized by ensureSnippetsMapThen(), so |
|
188 // be sure its callback returned before trying to use it. |
|
189 let gSnippetsMap; |
|
190 let gSnippetsMapCallbacks = []; |
|
191 |
|
192 /** |
|
193 * Ensure the snippets map is properly initialized. |
|
194 * |
|
195 * @param aCallback |
|
196 * Invoked once the map has been initialized, gets the map as argument. |
|
197 * @note Snippets should never directly manage the underlying storage, since |
|
198 * it may change inadvertently. |
|
199 */ |
|
200 function ensureSnippetsMapThen(aCallback) |
|
201 { |
|
202 if (gSnippetsMap) { |
|
203 aCallback(gSnippetsMap); |
|
204 return; |
|
205 } |
|
206 |
|
207 // Handle multiple requests during the async initialization. |
|
208 gSnippetsMapCallbacks.push(aCallback); |
|
209 if (gSnippetsMapCallbacks.length > 1) { |
|
210 // We are already updating, the callbacks will be invoked when done. |
|
211 return; |
|
212 } |
|
213 |
|
214 let invokeCallbacks = function () { |
|
215 if (!gSnippetsMap) { |
|
216 gSnippetsMap = Object.freeze(new Map()); |
|
217 } |
|
218 |
|
219 for (let callback of gSnippetsMapCallbacks) { |
|
220 callback(gSnippetsMap); |
|
221 } |
|
222 gSnippetsMapCallbacks.length = 0; |
|
223 } |
|
224 |
|
225 let openRequest = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); |
|
226 |
|
227 openRequest.onerror = function (event) { |
|
228 // Try to delete the old database so that we can start this process over |
|
229 // next time. |
|
230 indexedDB.deleteDatabase(DATABASE_NAME); |
|
231 invokeCallbacks(); |
|
232 }; |
|
233 |
|
234 openRequest.onupgradeneeded = function (event) { |
|
235 let db = event.target.result; |
|
236 if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) { |
|
237 db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME); |
|
238 } |
|
239 } |
|
240 |
|
241 openRequest.onsuccess = function (event) { |
|
242 let db = event.target.result; |
|
243 |
|
244 db.onerror = function (event) { |
|
245 invokeCallbacks(); |
|
246 } |
|
247 |
|
248 db.onversionchange = function (event) { |
|
249 event.target.close(); |
|
250 invokeCallbacks(); |
|
251 } |
|
252 |
|
253 let cache = new Map(); |
|
254 let cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME) |
|
255 .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor(); |
|
256 cursorRequest.onerror = function (event) { |
|
257 invokeCallbacks(); |
|
258 } |
|
259 |
|
260 cursorRequest.onsuccess = function(event) { |
|
261 let cursor = event.target.result; |
|
262 |
|
263 // Populate the cache from the persistent storage. |
|
264 if (cursor) { |
|
265 cache.set(cursor.key, cursor.value); |
|
266 cursor.continue(); |
|
267 return; |
|
268 } |
|
269 |
|
270 // The cache has been filled up, create the snippets map. |
|
271 gSnippetsMap = Object.freeze({ |
|
272 get: function (aKey) cache.get(aKey), |
|
273 set: function (aKey, aValue) { |
|
274 db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") |
|
275 .objectStore(SNIPPETS_OBJECTSTORE_NAME).put(aValue, aKey); |
|
276 return cache.set(aKey, aValue); |
|
277 }, |
|
278 has: function (aKey) cache.has(aKey), |
|
279 delete: function (aKey) { |
|
280 db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") |
|
281 .objectStore(SNIPPETS_OBJECTSTORE_NAME).delete(aKey); |
|
282 return cache.delete(aKey); |
|
283 }, |
|
284 clear: function () { |
|
285 db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") |
|
286 .objectStore(SNIPPETS_OBJECTSTORE_NAME).clear(); |
|
287 return cache.clear(); |
|
288 }, |
|
289 get size() cache.size |
|
290 }); |
|
291 |
|
292 setTimeout(invokeCallbacks, 0); |
|
293 } |
|
294 } |
|
295 } |
|
296 |
|
297 function onSearchSubmit(aEvent) |
|
298 { |
|
299 let searchTerms = document.getElementById("searchText").value; |
|
300 let engineName = document.documentElement.getAttribute("searchEngineName"); |
|
301 |
|
302 if (engineName && searchTerms.length > 0) { |
|
303 // Send an event that will perform a search and Firefox Health Report will |
|
304 // record that a search from about:home has occurred. |
|
305 let eventData = JSON.stringify({ |
|
306 engineName: engineName, |
|
307 searchTerms: searchTerms |
|
308 }); |
|
309 let event = new CustomEvent("AboutHomeSearchEvent", {detail: eventData}); |
|
310 document.dispatchEvent(event); |
|
311 } |
|
312 |
|
313 aEvent.preventDefault(); |
|
314 } |
|
315 |
|
316 |
|
317 function setupSearchEngine() |
|
318 { |
|
319 // The "autofocus" attribute doesn't focus the form element |
|
320 // immediately when the element is first drawn, so the |
|
321 // attribute is also used for styling when the page first loads. |
|
322 let searchText = document.getElementById("searchText"); |
|
323 searchText.addEventListener("blur", function searchText_onBlur() { |
|
324 searchText.removeEventListener("blur", searchText_onBlur); |
|
325 searchText.removeAttribute("autofocus"); |
|
326 }); |
|
327 |
|
328 let searchEngineName = document.documentElement.getAttribute("searchEngineName"); |
|
329 let searchEngineInfo = SEARCH_ENGINES[searchEngineName]; |
|
330 let logoElt = document.getElementById("searchEngineLogo"); |
|
331 |
|
332 // Add search engine logo. |
|
333 if (searchEngineInfo && searchEngineInfo.image) { |
|
334 logoElt.parentNode.hidden = false; |
|
335 logoElt.src = searchEngineInfo.image; |
|
336 logoElt.alt = searchEngineName; |
|
337 searchText.placeholder = ""; |
|
338 } |
|
339 else { |
|
340 logoElt.parentNode.hidden = true; |
|
341 searchText.placeholder = searchEngineName; |
|
342 } |
|
343 |
|
344 } |
|
345 |
|
346 /** |
|
347 * Inform the test harness that we're done loading the page. |
|
348 */ |
|
349 function loadSucceeded() |
|
350 { |
|
351 var event = new CustomEvent("AboutHomeLoadSnippetsSucceeded", {bubbles:true}); |
|
352 document.dispatchEvent(event); |
|
353 } |
|
354 |
|
355 /** |
|
356 * Update the local snippets from the remote storage, then show them through |
|
357 * showSnippets. |
|
358 */ |
|
359 function loadSnippets() |
|
360 { |
|
361 if (!gSnippetsMap) |
|
362 throw new Error("Snippets map has not properly been initialized"); |
|
363 |
|
364 // Allow tests to modify the snippets map before using it. |
|
365 var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true}); |
|
366 document.dispatchEvent(event); |
|
367 |
|
368 // Check cached snippets version. |
|
369 let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0; |
|
370 let currentVersion = document.documentElement.getAttribute("snippetsVersion"); |
|
371 if (cachedVersion < currentVersion) { |
|
372 // The cached snippets are old and unsupported, restart from scratch. |
|
373 gSnippetsMap.clear(); |
|
374 } |
|
375 |
|
376 // Check last snippets update. |
|
377 let lastUpdate = gSnippetsMap.get("snippets-last-update"); |
|
378 let updateURL = document.documentElement.getAttribute("snippetsURL"); |
|
379 let shouldUpdate = !lastUpdate || |
|
380 Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS; |
|
381 if (updateURL && shouldUpdate) { |
|
382 // Try to update from network. |
|
383 let xhr = new XMLHttpRequest(); |
|
384 try { |
|
385 xhr.open("GET", updateURL, true); |
|
386 } catch (ex) { |
|
387 showSnippets(); |
|
388 loadSucceeded(); |
|
389 return; |
|
390 } |
|
391 // Even if fetching should fail we don't want to spam the server, thus |
|
392 // set the last update time regardless its results. Will retry tomorrow. |
|
393 gSnippetsMap.set("snippets-last-update", Date.now()); |
|
394 xhr.onerror = function (event) { |
|
395 showSnippets(); |
|
396 }; |
|
397 xhr.onload = function (event) |
|
398 { |
|
399 if (xhr.status == 200) { |
|
400 gSnippetsMap.set("snippets", xhr.responseText); |
|
401 gSnippetsMap.set("snippets-cached-version", currentVersion); |
|
402 } |
|
403 showSnippets(); |
|
404 loadSucceeded(); |
|
405 }; |
|
406 xhr.send(null); |
|
407 } else { |
|
408 showSnippets(); |
|
409 loadSucceeded(); |
|
410 } |
|
411 } |
|
412 |
|
413 /** |
|
414 * Shows locally cached remote snippets, or default ones when not available. |
|
415 * |
|
416 * @note: snippets should never invoke showSnippets(), or they may cause |
|
417 * a "too much recursion" exception. |
|
418 */ |
|
419 let _snippetsShown = false; |
|
420 function showSnippets() |
|
421 { |
|
422 let snippetsElt = document.getElementById("snippets"); |
|
423 |
|
424 // Show about:rights notification, if needed. |
|
425 let showRights = document.documentElement.getAttribute("showKnowYourRights"); |
|
426 if (showRights) { |
|
427 let rightsElt = document.getElementById("rightsSnippet"); |
|
428 let anchor = rightsElt.getElementsByTagName("a")[0]; |
|
429 anchor.href = "about:rights"; |
|
430 snippetsElt.appendChild(rightsElt); |
|
431 rightsElt.removeAttribute("hidden"); |
|
432 return; |
|
433 } |
|
434 |
|
435 if (!gSnippetsMap) |
|
436 throw new Error("Snippets map has not properly been initialized"); |
|
437 if (_snippetsShown) { |
|
438 // There's something wrong with the remote snippets, just in case fall back |
|
439 // to the default snippets. |
|
440 showDefaultSnippets(); |
|
441 throw new Error("showSnippets should never be invoked multiple times"); |
|
442 } |
|
443 _snippetsShown = true; |
|
444 |
|
445 let snippets = gSnippetsMap.get("snippets"); |
|
446 // If there are remotely fetched snippets, try to to show them. |
|
447 if (snippets) { |
|
448 // Injecting snippets can throw if they're invalid XML. |
|
449 try { |
|
450 snippetsElt.innerHTML = snippets; |
|
451 // Scripts injected by innerHTML are inactive, so we have to relocate them |
|
452 // through DOM manipulation to activate their contents. |
|
453 Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) { |
|
454 let relocatedScript = document.createElement("script"); |
|
455 relocatedScript.type = "text/javascript;version=1.8"; |
|
456 relocatedScript.text = elt.text; |
|
457 elt.parentNode.replaceChild(relocatedScript, elt); |
|
458 }); |
|
459 return; |
|
460 } catch (ex) { |
|
461 // Bad content, continue to show default snippets. |
|
462 } |
|
463 } |
|
464 |
|
465 showDefaultSnippets(); |
|
466 } |
|
467 |
|
468 /** |
|
469 * Clear snippets element contents and show default snippets. |
|
470 */ |
|
471 function showDefaultSnippets() |
|
472 { |
|
473 // Clear eventual contents... |
|
474 let snippetsElt = document.getElementById("snippets"); |
|
475 snippetsElt.innerHTML = ""; |
|
476 |
|
477 // ...then show default snippets. |
|
478 let defaultSnippetsElt = document.getElementById("defaultSnippets"); |
|
479 let entries = defaultSnippetsElt.querySelectorAll("span"); |
|
480 // Choose a random snippet. Assume there is always at least one. |
|
481 let randIndex = Math.floor(Math.random() * entries.length); |
|
482 let entry = entries[randIndex]; |
|
483 // Inject url in the eventual link. |
|
484 if (DEFAULT_SNIPPETS_URLS[randIndex]) { |
|
485 let links = entry.getElementsByTagName("a"); |
|
486 // Default snippets can have only one link, otherwise something is messed |
|
487 // up in the translation. |
|
488 if (links.length == 1) { |
|
489 links[0].href = DEFAULT_SNIPPETS_URLS[randIndex]; |
|
490 } |
|
491 } |
|
492 // Move the default snippet to the snippets element. |
|
493 snippetsElt.appendChild(entry); |
|
494 } |
|
495 |
|
496 function fitToWidth() { |
|
497 if (window.scrollMaxX) { |
|
498 document.body.setAttribute("narrow", "true"); |
|
499 } else if (document.body.hasAttribute("narrow")) { |
|
500 document.body.removeAttribute("narrow"); |
|
501 fitToWidth(); |
|
502 } |
|
503 } |