Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | "use strict"; |
michael@0 | 7 | |
michael@0 | 8 | this.EXPORTED_SYMBOLS = ["Home"]; |
michael@0 | 9 | |
michael@0 | 10 | const { classes: Cc, interfaces: Ci, utils: Cu } = Components; |
michael@0 | 11 | |
michael@0 | 12 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 13 | Cu.import("resource://gre/modules/SharedPreferences.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/Messaging.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | // Keep this in sync with the constant defined in PanelAuthCache.java |
michael@0 | 17 | const PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_"; |
michael@0 | 18 | |
michael@0 | 19 | // See bug 915424 |
michael@0 | 20 | function resolveGeckoURI(aURI) { |
michael@0 | 21 | if (!aURI) |
michael@0 | 22 | throw "Can't resolve an empty uri"; |
michael@0 | 23 | |
michael@0 | 24 | if (aURI.startsWith("chrome://")) { |
michael@0 | 25 | let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]); |
michael@0 | 26 | return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec; |
michael@0 | 27 | } else if (aURI.startsWith("resource://")) { |
michael@0 | 28 | let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); |
michael@0 | 29 | return handler.resolveURI(Services.io.newURI(aURI, null, null)); |
michael@0 | 30 | } |
michael@0 | 31 | return aURI; |
michael@0 | 32 | } |
michael@0 | 33 | |
michael@0 | 34 | function BannerMessage(options) { |
michael@0 | 35 | let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); |
michael@0 | 36 | this.id = uuidgen.generateUUID().toString(); |
michael@0 | 37 | |
michael@0 | 38 | if ("text" in options && options.text != null) |
michael@0 | 39 | this.text = options.text; |
michael@0 | 40 | |
michael@0 | 41 | if ("icon" in options && options.icon != null) |
michael@0 | 42 | this.iconURI = resolveGeckoURI(options.icon); |
michael@0 | 43 | |
michael@0 | 44 | if ("onshown" in options && typeof options.onshown === "function") |
michael@0 | 45 | this.onshown = options.onshown; |
michael@0 | 46 | |
michael@0 | 47 | if ("onclick" in options && typeof options.onclick === "function") |
michael@0 | 48 | this.onclick = options.onclick; |
michael@0 | 49 | |
michael@0 | 50 | if ("ondismiss" in options && typeof options.ondismiss === "function") |
michael@0 | 51 | this.ondismiss = options.ondismiss; |
michael@0 | 52 | } |
michael@0 | 53 | |
michael@0 | 54 | // We need this object to have access to the HomeBanner |
michael@0 | 55 | // private members without leaking it outside Home.jsm. |
michael@0 | 56 | let HomeBannerMessageHandlers; |
michael@0 | 57 | |
michael@0 | 58 | let HomeBanner = (function () { |
michael@0 | 59 | // Whether there is a "HomeBanner:Get" request we couldn't fulfill. |
michael@0 | 60 | let _pendingRequest = false; |
michael@0 | 61 | |
michael@0 | 62 | // Functions used to handle messages sent from Java. |
michael@0 | 63 | HomeBannerMessageHandlers = { |
michael@0 | 64 | "HomeBanner:Get": function handleBannerGet(data) { |
michael@0 | 65 | if (!_sendBannerData()) { |
michael@0 | 66 | _pendingRequest = true; |
michael@0 | 67 | } |
michael@0 | 68 | } |
michael@0 | 69 | }; |
michael@0 | 70 | |
michael@0 | 71 | // Holds the messages that will rotate through the banner. |
michael@0 | 72 | let _messages = {}; |
michael@0 | 73 | |
michael@0 | 74 | let _sendBannerData = function() { |
michael@0 | 75 | let keys = Object.keys(_messages); |
michael@0 | 76 | if (!keys.length) { |
michael@0 | 77 | return false; |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | // Choose a message at random. |
michael@0 | 81 | let randomId = keys[Math.floor(Math.random() * keys.length)]; |
michael@0 | 82 | let message = _messages[randomId]; |
michael@0 | 83 | |
michael@0 | 84 | sendMessageToJava({ |
michael@0 | 85 | type: "HomeBanner:Data", |
michael@0 | 86 | id: message.id, |
michael@0 | 87 | text: message.text, |
michael@0 | 88 | iconURI: message.iconURI |
michael@0 | 89 | }); |
michael@0 | 90 | return true; |
michael@0 | 91 | }; |
michael@0 | 92 | |
michael@0 | 93 | let _handleShown = function(id) { |
michael@0 | 94 | let message = _messages[id]; |
michael@0 | 95 | if (message.onshown) |
michael@0 | 96 | message.onshown(); |
michael@0 | 97 | }; |
michael@0 | 98 | |
michael@0 | 99 | let _handleClick = function(id) { |
michael@0 | 100 | let message = _messages[id]; |
michael@0 | 101 | if (message.onclick) |
michael@0 | 102 | message.onclick(); |
michael@0 | 103 | }; |
michael@0 | 104 | |
michael@0 | 105 | let _handleDismiss = function(id) { |
michael@0 | 106 | let message = _messages[id]; |
michael@0 | 107 | if (message.ondismiss) |
michael@0 | 108 | message.ondismiss(); |
michael@0 | 109 | }; |
michael@0 | 110 | |
michael@0 | 111 | return Object.freeze({ |
michael@0 | 112 | observe: function(subject, topic, data) { |
michael@0 | 113 | switch(topic) { |
michael@0 | 114 | case "HomeBanner:Shown": |
michael@0 | 115 | _handleShown(data); |
michael@0 | 116 | break; |
michael@0 | 117 | |
michael@0 | 118 | case "HomeBanner:Click": |
michael@0 | 119 | _handleClick(data); |
michael@0 | 120 | break; |
michael@0 | 121 | |
michael@0 | 122 | case "HomeBanner:Dismiss": |
michael@0 | 123 | _handleDismiss(data); |
michael@0 | 124 | break; |
michael@0 | 125 | } |
michael@0 | 126 | }, |
michael@0 | 127 | |
michael@0 | 128 | /** |
michael@0 | 129 | * Adds a new banner message to the rotation. |
michael@0 | 130 | * |
michael@0 | 131 | * @return id Unique identifer for the message. |
michael@0 | 132 | */ |
michael@0 | 133 | add: function(options) { |
michael@0 | 134 | let message = new BannerMessage(options); |
michael@0 | 135 | _messages[message.id] = message; |
michael@0 | 136 | |
michael@0 | 137 | // If this is the first message we're adding, add |
michael@0 | 138 | // observers to listen for requests from the Java UI. |
michael@0 | 139 | if (Object.keys(_messages).length == 1) { |
michael@0 | 140 | Services.obs.addObserver(this, "HomeBanner:Shown", false); |
michael@0 | 141 | Services.obs.addObserver(this, "HomeBanner:Click", false); |
michael@0 | 142 | Services.obs.addObserver(this, "HomeBanner:Dismiss", false); |
michael@0 | 143 | |
michael@0 | 144 | // Send a message to Java if there's a pending "HomeBanner:Get" request. |
michael@0 | 145 | if (_pendingRequest) { |
michael@0 | 146 | _pendingRequest = false; |
michael@0 | 147 | _sendBannerData(); |
michael@0 | 148 | } |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | return message.id; |
michael@0 | 152 | }, |
michael@0 | 153 | |
michael@0 | 154 | /** |
michael@0 | 155 | * Removes a banner message from the rotation. |
michael@0 | 156 | * |
michael@0 | 157 | * @param id The id of the message to remove. |
michael@0 | 158 | */ |
michael@0 | 159 | remove: function(id) { |
michael@0 | 160 | if (!(id in _messages)) { |
michael@0 | 161 | throw "Home.banner: Can't remove message that doesn't exist: id = " + id; |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | delete _messages[id]; |
michael@0 | 165 | |
michael@0 | 166 | // If there are no more messages, remove the observers. |
michael@0 | 167 | if (Object.keys(_messages).length == 0) { |
michael@0 | 168 | Services.obs.removeObserver(this, "HomeBanner:Shown"); |
michael@0 | 169 | Services.obs.removeObserver(this, "HomeBanner:Click"); |
michael@0 | 170 | Services.obs.removeObserver(this, "HomeBanner:Dismiss"); |
michael@0 | 171 | } |
michael@0 | 172 | } |
michael@0 | 173 | }); |
michael@0 | 174 | })(); |
michael@0 | 175 | |
michael@0 | 176 | // We need this object to have access to the HomePanels |
michael@0 | 177 | // private members without leaking it outside Home.jsm. |
michael@0 | 178 | let HomePanelsMessageHandlers; |
michael@0 | 179 | |
michael@0 | 180 | let HomePanels = (function () { |
michael@0 | 181 | // Functions used to handle messages sent from Java. |
michael@0 | 182 | HomePanelsMessageHandlers = { |
michael@0 | 183 | |
michael@0 | 184 | "HomePanels:Get": function handlePanelsGet(data) { |
michael@0 | 185 | data = JSON.parse(data); |
michael@0 | 186 | |
michael@0 | 187 | let requestId = data.requestId; |
michael@0 | 188 | let ids = data.ids || null; |
michael@0 | 189 | |
michael@0 | 190 | let panels = []; |
michael@0 | 191 | for (let id in _registeredPanels) { |
michael@0 | 192 | // Null ids means we want to fetch all available panels |
michael@0 | 193 | if (ids == null || ids.indexOf(id) >= 0) { |
michael@0 | 194 | try { |
michael@0 | 195 | panels.push(_generatePanel(id)); |
michael@0 | 196 | } catch(e) { |
michael@0 | 197 | Cu.reportError("Home.panels: Invalid options, panel.id = " + id + ": " + e); |
michael@0 | 198 | } |
michael@0 | 199 | } |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | sendMessageToJava({ |
michael@0 | 203 | type: "HomePanels:Data", |
michael@0 | 204 | panels: panels, |
michael@0 | 205 | requestId: requestId |
michael@0 | 206 | }); |
michael@0 | 207 | }, |
michael@0 | 208 | |
michael@0 | 209 | "HomePanels:Authenticate": function handlePanelsAuthenticate(id) { |
michael@0 | 210 | // Generate panel options to get auth handler. |
michael@0 | 211 | let options = _registeredPanels[id](); |
michael@0 | 212 | if (!options.auth) { |
michael@0 | 213 | throw "Home.panels: Invalid auth for panel.id = " + id; |
michael@0 | 214 | } |
michael@0 | 215 | if (!options.auth.authenticate || typeof options.auth.authenticate !== "function") { |
michael@0 | 216 | throw "Home.panels: Invalid auth authenticate function: panel.id = " + this.id; |
michael@0 | 217 | } |
michael@0 | 218 | options.auth.authenticate(); |
michael@0 | 219 | }, |
michael@0 | 220 | |
michael@0 | 221 | "HomePanels:RefreshView": function handlePanelsRefreshView(data) { |
michael@0 | 222 | data = JSON.parse(data); |
michael@0 | 223 | |
michael@0 | 224 | let options = _registeredPanels[data.panelId](); |
michael@0 | 225 | let view = options.views[data.viewIndex]; |
michael@0 | 226 | |
michael@0 | 227 | if (!view) { |
michael@0 | 228 | throw "Home.panels: Invalid view for panel.id = " + data.panelId |
michael@0 | 229 | + ", view.index = " + data.viewIndex; |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | if (!view.onrefresh || typeof view.onrefresh !== "function") { |
michael@0 | 233 | throw "Home.panels: Invalid onrefresh for panel.id = " + data.panelId |
michael@0 | 234 | + ", view.index = " + data.viewIndex; |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | view.onrefresh(); |
michael@0 | 238 | }, |
michael@0 | 239 | |
michael@0 | 240 | "HomePanels:Installed": function handlePanelsInstalled(id) { |
michael@0 | 241 | _assertPanelExists(id); |
michael@0 | 242 | |
michael@0 | 243 | let options = _registeredPanels[id](); |
michael@0 | 244 | if (!options.oninstall) { |
michael@0 | 245 | return; |
michael@0 | 246 | } |
michael@0 | 247 | if (typeof options.oninstall !== "function") { |
michael@0 | 248 | throw "Home.panels: Invalid oninstall function: panel.id = " + this.id; |
michael@0 | 249 | } |
michael@0 | 250 | options.oninstall(); |
michael@0 | 251 | }, |
michael@0 | 252 | |
michael@0 | 253 | "HomePanels:Uninstalled": function handlePanelsUninstalled(id) { |
michael@0 | 254 | _assertPanelExists(id); |
michael@0 | 255 | |
michael@0 | 256 | let options = _registeredPanels[id](); |
michael@0 | 257 | if (!options.onuninstall) { |
michael@0 | 258 | return; |
michael@0 | 259 | } |
michael@0 | 260 | if (typeof options.onuninstall !== "function") { |
michael@0 | 261 | throw "Home.panels: Invalid onuninstall function: panel.id = " + this.id; |
michael@0 | 262 | } |
michael@0 | 263 | options.onuninstall(); |
michael@0 | 264 | } |
michael@0 | 265 | }; |
michael@0 | 266 | |
michael@0 | 267 | // Holds the current set of registered panels that can be |
michael@0 | 268 | // installed, updated, uninstalled, or unregistered. It maps |
michael@0 | 269 | // panel ids with the functions that dynamically generate |
michael@0 | 270 | // their respective panel options. This is used to retrieve |
michael@0 | 271 | // the current list of available panels in the system. |
michael@0 | 272 | // See HomePanels:Get handler. |
michael@0 | 273 | let _registeredPanels = {}; |
michael@0 | 274 | |
michael@0 | 275 | // Valid layouts for a panel. |
michael@0 | 276 | let Layout = Object.freeze({ |
michael@0 | 277 | FRAME: "frame" |
michael@0 | 278 | }); |
michael@0 | 279 | |
michael@0 | 280 | // Valid types of views for a dataset. |
michael@0 | 281 | let View = Object.freeze({ |
michael@0 | 282 | LIST: "list", |
michael@0 | 283 | GRID: "grid" |
michael@0 | 284 | }); |
michael@0 | 285 | |
michael@0 | 286 | // Valid item types for a panel view. |
michael@0 | 287 | let Item = Object.freeze({ |
michael@0 | 288 | ARTICLE: "article", |
michael@0 | 289 | IMAGE: "image" |
michael@0 | 290 | }); |
michael@0 | 291 | |
michael@0 | 292 | // Valid item handlers for a panel view. |
michael@0 | 293 | let ItemHandler = Object.freeze({ |
michael@0 | 294 | BROWSER: "browser", |
michael@0 | 295 | INTENT: "intent" |
michael@0 | 296 | }); |
michael@0 | 297 | |
michael@0 | 298 | function Panel(id, options) { |
michael@0 | 299 | this.id = id; |
michael@0 | 300 | this.title = options.title; |
michael@0 | 301 | this.layout = options.layout; |
michael@0 | 302 | this.views = options.views; |
michael@0 | 303 | |
michael@0 | 304 | if (!this.id || !this.title) { |
michael@0 | 305 | throw "Home.panels: Can't create a home panel without an id and title!"; |
michael@0 | 306 | } |
michael@0 | 307 | |
michael@0 | 308 | if (!this.layout) { |
michael@0 | 309 | // Use FRAME layout by default |
michael@0 | 310 | this.layout = Layout.FRAME; |
michael@0 | 311 | } else if (!_valueExists(Layout, this.layout)) { |
michael@0 | 312 | throw "Home.panels: Invalid layout for panel: panel.id = " + this.id + ", panel.layout =" + this.layout; |
michael@0 | 313 | } |
michael@0 | 314 | |
michael@0 | 315 | for (let view of this.views) { |
michael@0 | 316 | if (!_valueExists(View, view.type)) { |
michael@0 | 317 | throw "Home.panels: Invalid view type: panel.id = " + this.id + ", view.type = " + view.type; |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | if (!view.itemType) { |
michael@0 | 321 | if (view.type == View.LIST) { |
michael@0 | 322 | // Use ARTICLE item type by default in LIST views |
michael@0 | 323 | view.itemType = Item.ARTICLE; |
michael@0 | 324 | } else if (view.type == View.GRID) { |
michael@0 | 325 | // Use IMAGE item type by default in GRID views |
michael@0 | 326 | view.itemType = Item.IMAGE; |
michael@0 | 327 | } |
michael@0 | 328 | } else if (!_valueExists(Item, view.itemType)) { |
michael@0 | 329 | throw "Home.panels: Invalid item type: panel.id = " + this.id + ", view.itemType = " + view.itemType; |
michael@0 | 330 | } |
michael@0 | 331 | |
michael@0 | 332 | if (!view.itemHandler) { |
michael@0 | 333 | // Use BROWSER item handler by default |
michael@0 | 334 | view.itemHandler = ItemHandler.BROWSER; |
michael@0 | 335 | } else if (!_valueExists(ItemHandler, view.itemHandler)) { |
michael@0 | 336 | throw "Home.panels: Invalid item handler: panel.id = " + this.id + ", view.itemHandler = " + view.itemHandler; |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | if (!view.dataset) { |
michael@0 | 340 | throw "Home.panels: No dataset provided for view: panel.id = " + this.id + ", view.type = " + view.type; |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | if (view.onrefresh) { |
michael@0 | 344 | view.refreshEnabled = true; |
michael@0 | 345 | } |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | if (options.auth) { |
michael@0 | 349 | if (!options.auth.messageText) { |
michael@0 | 350 | throw "Home.panels: Invalid auth messageText: panel.id = " + this.id; |
michael@0 | 351 | } |
michael@0 | 352 | if (!options.auth.buttonText) { |
michael@0 | 353 | throw "Home.panels: Invalid auth buttonText: panel.id = " + this.id; |
michael@0 | 354 | } |
michael@0 | 355 | |
michael@0 | 356 | this.authConfig = { |
michael@0 | 357 | messageText: options.auth.messageText, |
michael@0 | 358 | buttonText: options.auth.buttonText |
michael@0 | 359 | }; |
michael@0 | 360 | |
michael@0 | 361 | // Include optional image URL if it is specified. |
michael@0 | 362 | if (options.auth.imageUrl) { |
michael@0 | 363 | this.authConfig.imageUrl = options.auth.imageUrl; |
michael@0 | 364 | } |
michael@0 | 365 | } |
michael@0 | 366 | } |
michael@0 | 367 | |
michael@0 | 368 | let _generatePanel = function(id) { |
michael@0 | 369 | let options = _registeredPanels[id](); |
michael@0 | 370 | return new Panel(id, options); |
michael@0 | 371 | }; |
michael@0 | 372 | |
michael@0 | 373 | // Helper function used to see if a value is in an object. |
michael@0 | 374 | let _valueExists = function(obj, value) { |
michael@0 | 375 | for (let key in obj) { |
michael@0 | 376 | if (obj[key] == value) { |
michael@0 | 377 | return true; |
michael@0 | 378 | } |
michael@0 | 379 | } |
michael@0 | 380 | return false; |
michael@0 | 381 | }; |
michael@0 | 382 | |
michael@0 | 383 | let _assertPanelExists = function(id) { |
michael@0 | 384 | if (!(id in _registeredPanels)) { |
michael@0 | 385 | throw "Home.panels: Panel doesn't exist: id = " + id; |
michael@0 | 386 | } |
michael@0 | 387 | }; |
michael@0 | 388 | |
michael@0 | 389 | return Object.freeze({ |
michael@0 | 390 | Layout: Layout, |
michael@0 | 391 | View: View, |
michael@0 | 392 | Item: Item, |
michael@0 | 393 | ItemHandler: ItemHandler, |
michael@0 | 394 | |
michael@0 | 395 | register: function(id, optionsCallback) { |
michael@0 | 396 | // Bail if the panel already exists |
michael@0 | 397 | if (id in _registeredPanels) { |
michael@0 | 398 | throw "Home.panels: Panel already exists: id = " + id; |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | if (!optionsCallback || typeof optionsCallback !== "function") { |
michael@0 | 402 | throw "Home.panels: Panel callback must be a function: id = " + id; |
michael@0 | 403 | } |
michael@0 | 404 | |
michael@0 | 405 | _registeredPanels[id] = optionsCallback; |
michael@0 | 406 | }, |
michael@0 | 407 | |
michael@0 | 408 | unregister: function(id) { |
michael@0 | 409 | _assertPanelExists(id); |
michael@0 | 410 | |
michael@0 | 411 | delete _registeredPanels[id]; |
michael@0 | 412 | }, |
michael@0 | 413 | |
michael@0 | 414 | install: function(id) { |
michael@0 | 415 | _assertPanelExists(id); |
michael@0 | 416 | |
michael@0 | 417 | sendMessageToJava({ |
michael@0 | 418 | type: "HomePanels:Install", |
michael@0 | 419 | panel: _generatePanel(id) |
michael@0 | 420 | }); |
michael@0 | 421 | }, |
michael@0 | 422 | |
michael@0 | 423 | uninstall: function(id) { |
michael@0 | 424 | _assertPanelExists(id); |
michael@0 | 425 | |
michael@0 | 426 | sendMessageToJava({ |
michael@0 | 427 | type: "HomePanels:Uninstall", |
michael@0 | 428 | id: id |
michael@0 | 429 | }); |
michael@0 | 430 | }, |
michael@0 | 431 | |
michael@0 | 432 | update: function(id) { |
michael@0 | 433 | _assertPanelExists(id); |
michael@0 | 434 | |
michael@0 | 435 | sendMessageToJava({ |
michael@0 | 436 | type: "HomePanels:Update", |
michael@0 | 437 | panel: _generatePanel(id) |
michael@0 | 438 | }); |
michael@0 | 439 | }, |
michael@0 | 440 | |
michael@0 | 441 | setAuthenticated: function(id, isAuthenticated) { |
michael@0 | 442 | _assertPanelExists(id); |
michael@0 | 443 | |
michael@0 | 444 | let authKey = PREFS_PANEL_AUTH_PREFIX + id; |
michael@0 | 445 | let sharedPrefs = new SharedPreferences(); |
michael@0 | 446 | sharedPrefs.setBoolPref(authKey, isAuthenticated); |
michael@0 | 447 | } |
michael@0 | 448 | }); |
michael@0 | 449 | })(); |
michael@0 | 450 | |
michael@0 | 451 | // Public API |
michael@0 | 452 | this.Home = Object.freeze({ |
michael@0 | 453 | banner: HomeBanner, |
michael@0 | 454 | panels: HomePanels, |
michael@0 | 455 | |
michael@0 | 456 | // Lazy notification observer registered in browser.js |
michael@0 | 457 | observe: function(subject, topic, data) { |
michael@0 | 458 | if (topic in HomeBannerMessageHandlers) { |
michael@0 | 459 | HomeBannerMessageHandlers[topic](data); |
michael@0 | 460 | } else if (topic in HomePanelsMessageHandlers) { |
michael@0 | 461 | HomePanelsMessageHandlers[topic](data); |
michael@0 | 462 | } else { |
michael@0 | 463 | Cu.reportError("Home.observe: message handler not found for topic: " + topic); |
michael@0 | 464 | } |
michael@0 | 465 | } |
michael@0 | 466 | }); |