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