browser_sanitizeDialog.js
Go to the documentation of this file.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* ***** BEGIN LICENSE BLOCK *****
4  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5  *
6  * The contents of this file are subject to the Mozilla Public License Version
7  * 1.1 (the "License"); you may not use this file except in compliance with
8  * the License. You may obtain a copy of the License at
9  * http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the
14  * License.
15  *
16  * The Original Code is sanitize dialog test code.
17  *
18  * The Initial Developer of the Original Code is Mozilla Corp.
19  * Portions created by the Initial Developer are Copyright (C) 2009
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * Drew Willcoxon <adw@mozilla.com> (Original Author)
24  * Ehsan Akhgari <ehsan.akhgari@gmail.com>
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either the GNU General Public License Version 2 or later (the "GPL"), or
28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
39 
53 Cc["@mozilla.org/moz/jssubscript-loader;1"].
54  getService(Components.interfaces.mozIJSSubScriptLoader).
55  loadSubScript("chrome://mochikit/content/MochiKit/packed.js");
56 
57 Cc["@mozilla.org/moz/jssubscript-loader;1"].
58  getService(Components.interfaces.mozIJSSubScriptLoader).
59  loadSubScript("chrome://browser/content/sanitize.js");
60 
61 const winWatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
62  getService(Ci.nsIWindowWatcher);
63 const dm = Cc["@mozilla.org/download-manager;1"].
64  getService(Ci.nsIDownloadManager);
65 const bhist = Cc["@mozilla.org/browser/global-history;2"].
66  getService(Ci.nsIBrowserHistory);
67 const formhist = Cc["@mozilla.org/satchel/form-history;1"].
68  getService(Ci.nsIFormHistory2);
69 
70 // Add tests here. Each is a function that's called by doNextTest().
71 var gAllTests = [
72 
76  function () {
77  let wh = new WindowHelper();
78  wh.onload = function () {
79  // Select "Last Hour"
80  this.selectDuration(Sanitizer.TIMESPAN_HOUR);
81  // Hide details
82  if (!this.getItemList().collapsed)
83  this.toggleDetails();
84  this.acceptDialog();
85  };
86  wh.open();
87  },
88 
92  function () {
93  // Add history (within the past hour)
94  let uris = [];
95  for (let i = 0; i < 30; i++) {
96  uris.push(addHistoryWithMinutesAgo(i));
97  }
98 
99  let wh = new WindowHelper();
100  wh.onload = function () {
101  this.selectDuration(Sanitizer.TIMESPAN_HOUR);
102  this.checkPrefCheckbox("history", false);
103  this.checkDetails(false);
104 
105  // Show details
106  this.toggleDetails();
107  this.checkDetails(true);
108 
109  // Hide details
110  this.toggleDetails();
111  this.checkDetails(false);
112  this.cancelDialog();
113 
114  ensureHistoryClearedState(uris, false);
115  blankSlate();
116  ensureHistoryClearedState(uris, true);
117  };
118  wh.open();
119  },
120 
125  function () {
126  // Add history and downloads (within the past hour).
127  let uris = [];
128  for (let i = 0; i < 30; i++) {
129  uris.push(addHistoryWithMinutesAgo(i));
130  }
131  let downloadIDs = [];
132  for (let i = 0; i < 5; i++) {
133  downloadIDs.push(addDownloadWithMinutesAgo(i));
134  }
135  // Add history and downloads (over an hour ago).
136  let olderURIs = [];
137  for (let i = 0; i < 5; i++) {
138  olderURIs.push(addHistoryWithMinutesAgo(61 + i));
139  }
140  let olderDownloadIDs = [];
141  for (let i = 0; i < 5; i++) {
142  olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
143  }
144  let totalHistoryVisits = uris.length + olderURIs.length;
145 
146  let wh = new WindowHelper();
147  wh.onload = function () {
148  this.selectDuration(Sanitizer.TIMESPAN_HOUR);
149  this.checkPrefCheckbox("history", true);
150  this.acceptDialog();
151 
152  intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
153  "timeSpan pref should be hour after accepting dialog with " +
154  "hour selected");
155  boolPrefIs("cpd.history", true,
156  "history pref should be true after accepting dialog with " +
157  "history checkbox checked");
158  boolPrefIs("cpd.downloads", true,
159  "downloads pref should be true after accepting dialog with " +
160  "history checkbox checked");
161 
162  // History visits and downloads within one hour should be cleared.
163  ensureHistoryClearedState(uris, true);
164  ensureDownloadsClearedState(downloadIDs, true);
165 
166  // Visits and downloads > 1 hour should still exist.
167  ensureHistoryClearedState(olderURIs, false);
168  ensureDownloadsClearedState(olderDownloadIDs, false);
169 
170  // OK, done, cleanup after ourselves.
171  blankSlate();
172  ensureHistoryClearedState(olderURIs, true);
173  ensureDownloadsClearedState(olderDownloadIDs, true);
174  };
175  wh.open();
176  },
177 
182  function () {
183  // Add history, downloads, form entries (within the past hour).
184  let uris = [];
185  for (let i = 0; i < 5; i++) {
186  uris.push(addHistoryWithMinutesAgo(i));
187  }
188  let downloadIDs = [];
189  for (let i = 0; i < 5; i++) {
190  downloadIDs.push(addDownloadWithMinutesAgo(i));
191  }
192  let formEntries = [];
193  for (let i = 0; i < 5; i++) {
194  formEntries.push(addFormEntryWithMinutesAgo(i));
195  }
196 
197  let wh = new WindowHelper();
198  wh.onload = function () {
199  is(this.isWarningPanelVisible(), false,
200  "Warning panel should be hidden after previously accepting dialog " +
201  "with a predefined timespan");
202  this.selectDuration(Sanitizer.TIMESPAN_HOUR);
203 
204  // Remove only form entries, leave history (including downloads).
205  this.checkPrefCheckbox("history", false);
206  this.checkPrefCheckbox("formdata", true);
207  this.acceptDialog();
208 
209  intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
210  "timeSpan pref should be hour after accepting dialog with " +
211  "hour selected");
212  boolPrefIs("cpd.history", false,
213  "history pref should be false after accepting dialog with " +
214  "history checkbox unchecked");
215  boolPrefIs("cpd.downloads", false,
216  "downloads pref should be false after accepting dialog with " +
217  "history checkbox unchecked");
218 
219  // Of the three only form entries should be cleared.
220  ensureHistoryClearedState(uris, false);
221  ensureDownloadsClearedState(downloadIDs, false);
222  ensureFormEntriesClearedState(formEntries, true);
223 
224  // OK, done, cleanup after ourselves.
225  blankSlate();
226  ensureHistoryClearedState(uris, true);
227  ensureDownloadsClearedState(downloadIDs, true);
228  };
229  wh.open();
230  },
231 
235  function () {
236  // Add history.
237  let uris = [];
238  uris.push(addHistoryWithMinutesAgo(10)); // within past hour
239  uris.push(addHistoryWithMinutesAgo(70)); // within past two hours
240  uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
241  uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
242 
243  let wh = new WindowHelper();
244  wh.onload = function () {
245  is(this.isWarningPanelVisible(), false,
246  "Warning panel should be hidden after previously accepting dialog " +
247  "with a predefined timespan");
248  this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
249  this.checkPrefCheckbox("history", true);
250  this.checkDetails(true);
251 
252  // Hide details
253  this.toggleDetails();
254  this.checkDetails(false);
255 
256  // Show details
257  this.toggleDetails();
258  this.checkDetails(true);
259 
260  this.acceptDialog();
261 
262  intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
263  "timeSpan pref should be everything after accepting dialog " +
264  "with everything selected");
265  ensureHistoryClearedState(uris, true);
266  };
267  wh.open();
268  },
269 
274  function () {
275  // Add history.
276  let uris = [];
277  uris.push(addHistoryWithMinutesAgo(10)); // within past hour
278  uris.push(addHistoryWithMinutesAgo(70)); // within past two hours
279  uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
280  uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
281 
282  let wh = new WindowHelper();
283  wh.onload = function () {
284  is(this.isWarningPanelVisible(), true,
285  "Warning panel should be visible after previously accepting dialog " +
286  "with clearing everything");
287  this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
288  this.checkPrefCheckbox("history", true);
289  this.acceptDialog();
290 
291  intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
292  "timeSpan pref should be everything after accepting dialog " +
293  "with everything selected");
294  ensureHistoryClearedState(uris, true);
295  };
296  wh.open();
297  },
298 
303  function () {
304  let wh = new WindowHelper();
305  wh.onload = function () {
306  // Check all items and select "Everything"
307  this.checkAllCheckboxes();
308  this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
309 
310  // Hide details
311  this.toggleDetails();
312  this.checkDetails(false);
313  this.acceptDialog();
314  };
315  wh.open();
316  },
317  function () {
318  let wh = new WindowHelper();
319  wh.onload = function () {
320  // Details should remain closed because all items are checked.
321  this.checkDetails(false);
322 
323  // Uncheck history.
324  this.checkPrefCheckbox("history", false);
325  this.acceptDialog();
326  };
327  wh.open();
328  },
329  function () {
330  let wh = new WindowHelper();
331  wh.onload = function () {
332  // Details should be open because not all items are checked.
333  this.checkDetails(true);
334 
335  // Modify the Site Preferences item state (bug 527820)
336  this.checkAllCheckboxes();
337  this.checkPrefCheckbox("siteSettings", false);
338  this.acceptDialog();
339  };
340  wh.open();
341  },
342  function () {
343  let wh = new WindowHelper();
344  wh.onload = function () {
345  // Details should be open because not all items are checked.
346  this.checkDetails(true);
347 
348  // Hide details
349  this.toggleDetails();
350  this.checkDetails(false);
351  this.cancelDialog();
352  };
353  wh.open();
354  },
355  function () {
356  let wh = new WindowHelper();
357  wh.onload = function () {
358  // Details should be open because not all items are checked.
359  this.checkDetails(true);
360 
361  // Select another duration
362  this.selectDuration(Sanitizer.TIMESPAN_HOUR);
363  // Hide details
364  this.toggleDetails();
365  this.checkDetails(false);
366  this.acceptDialog();
367  };
368  wh.open();
369  },
370  function () {
371  let wh = new WindowHelper();
372  wh.onload = function () {
373  // Details should not be open because "Last Hour" is selected
374  this.checkDetails(false);
375 
376  this.cancelDialog();
377  };
378  wh.open();
379  },
380  function () {
381  let wh = new WindowHelper();
382  wh.onload = function () {
383  // Details should have remained closed
384  this.checkDetails(false);
385 
386  // Show details
387  this.toggleDetails();
388  this.checkDetails(true);
389  this.cancelDialog();
390  };
391  wh.open();
392  }
393 ];
394 
395 // Used as the download database ID for a new download. Incremented for each
396 // new download. See addDownloadWithMinutesAgo().
397 var gDownloadId = 5555551;
398 
399 // Index in gAllTests of the test currently being run. Incremented for each
400 // test run. See doNextTest().
401 var gCurrTest = 0;
402 
403 var now_uSec = Date.now() * 1000;
404 
406 
414 function WindowHelper(aWin) {
415  this.win = aWin;
416 }
417 
418 WindowHelper.prototype = {
422  acceptDialog: function () {
423  is(this.win.document.documentElement.getButton("accept").disabled, false,
424  "Dialog's OK button should not be disabled");
425  this.win.document.documentElement.acceptDialog();
426  },
427 
431  cancelDialog: function () {
432  this.win.document.documentElement.cancelDialog();
433  },
434 
443  checkDetails: function (aShouldBeShown) {
444  let button = this.getDetailsButton();
445  let list = this.getItemList();
446  let hidden = list.hidden || list.collapsed;
447  is(hidden, !aShouldBeShown,
448  "Details should be " + (aShouldBeShown ? "shown" : "hidden") +
449  " but were actually " + (hidden ? "hidden" : "shown"));
450  let dir = hidden ? "down" : "up";
451  is(button.className, "expander-" + dir,
452  "Details button should be " + dir + " because item list is " +
453  (hidden ? "" : "not ") + "hidden");
454  let height = 0;
455  if (!hidden)
456  height += list.boxObject.height;
457  if (this.isWarningPanelVisible())
458  height += this.getWarningPanel().boxObject.height;
459  ok(height < this.win.innerHeight,
460  "Window should be tall enough to fit warning panel and item list");
461  },
462 
472  checkPrefCheckbox: function (aPrefName, aCheckState) {
473  var pref = "privacy.cpd." + aPrefName;
474  var cb = this.win.document.querySelectorAll(
475  "#itemList > [preference='" + pref + "']");
476  is(cb.length, 1, "found checkbox for " + pref + " preference");
477  if (cb[0].checked != aCheckState)
478  cb[0].click();
479  },
480 
484  checkAllCheckboxes: function () {
485  var cb = this.win.document.querySelectorAll("#itemList > [preference]");
486  ok(cb.length > 1, "found checkboxes for preferences");
487  for (var i = 0; i < cb.length; ++i) {
488  var pref = this.win.document.getElementById(cb[i].getAttribute("preference"));
489  if (!pref.value)
490  cb[i].click();
491  }
492  },
493 
497  getDetailsButton: function () {
498  return this.win.document.getElementById("detailsExpander");
499  },
500 
504  getDurationDropdown: function () {
505  return this.win.document.getElementById("sanitizeDurationChoice");
506  },
507 
511  getItemList: function () {
512  return this.win.document.getElementById("itemList");
513  },
514 
518  getWarningPanel: function () {
519  return this.win.document.getElementById("sanitizeEverythingWarningBox");
520  },
521 
526  isWarningPanelVisible: function () {
527  return !this.getWarningPanel().hidden;
528  },
529 
536  open: function () {
537  let wh = this;
538 
539  let windowObserver = {
540  observe: function(aSubject, aTopic, aData) {
541  if (aTopic !== "domwindowopened")
542  return;
543 
544  winWatch.unregisterNotification(this);
545 
546  var loaded = false;
547  let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
548 
549  win.addEventListener("load", function onload(event) {
550  win.removeEventListener("load", onload, false);
551 
552  if (win.name !== "SanitizeDialog")
553  return;
554 
555  wh.win = win;
556  loaded = true;
557 
558  executeSoon(function () {
559  // Some exceptions that reach here don't reach the test harness, but
560  // ok()/is() do...
561  try {
562  wh.onload();
563  }
564  catch (exc) {
565  win.close();
566  ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
567  finish();
568  }
569  });
570  }, false);
571 
572  win.addEventListener("unload", function onunload(event) {
573  if (win.name !== "SanitizeDialog") {
574  win.removeEventListener("unload", onunload, false);
575  return;
576  }
577 
578  // Why is unload fired before load?
579  if (!loaded)
580  return;
581 
582  win.removeEventListener("unload", onunload, false);
583  wh.win = win;
584 
585  executeSoon(function () {
586  // Some exceptions that reach here don't reach the test harness, but
587  // ok()/is() do...
588  try {
589  if (wh.onunload)
590  wh.onunload();
591  doNextTest();
592  }
593  catch (exc) {
594  win.close();
595  ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
596  finish();
597  }
598  });
599  }, false);
600  }
601  };
602  winWatch.registerNotification(windowObserver);
603  winWatch.openWindow(null,
604  "chrome://browser/content/sanitize.xul",
605  "SanitizeDialog",
606  "chrome,titlebar,dialog,centerscreen,modal",
607  null);
608  },
609 
616  selectDuration: function (aDurVal) {
617  this.getDurationDropdown().value = aDurVal;
618  if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
619  is(this.isWarningPanelVisible(), true,
620  "Warning panel should be visible for TIMESPAN_EVERYTHING");
621  }
622  else {
623  is(this.isWarningPanelVisible(), false,
624  "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
625  }
626  },
627 
631  toggleDetails: function () {
632  this.getDetailsButton().click();
633  }
634 };
635 
642 function addDownloadWithMinutesAgo(aMinutesAgo) {
643  let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
644  let data = {
645  id: gDownloadId,
646  name: name,
647  source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
648  target: name,
649  startTime: now_uSec - (aMinutesAgo * 60 * 1000000),
650  endTime: now_uSec - ((aMinutesAgo + 1) *60 * 1000000),
651  state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
652  currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
653  };
654 
655  let db = dm.DBConnection;
656  let stmt = db.createStatement(
657  "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
658  "state, currBytes, maxBytes, preferredAction, autoResume) " +
659  "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
660  ":currBytes, :maxBytes, :preferredAction, :autoResume)");
661  try {
662  for (let prop in data) {
663  stmt.params[prop] = data[prop];
664  }
665  stmt.execute();
666  }
667  finally {
668  stmt.reset();
669  }
670 
671  is(downloadExists(gDownloadId), true,
672  "Sanity check: download " + gDownloadId +
673  " should exist after creating it");
674 
675  return gDownloadId++;
676 }
677 
684 function addFormEntryWithMinutesAgo(aMinutesAgo) {
685  let name = aMinutesAgo + "-minutes-ago";
686  formhist.addEntry(name, "dummy");
687 
688  // Artifically age the entry to the proper vintage.
689  let db = formhist.DBConnection;
690  let timestamp = now_uSec - (aMinutesAgo * 60 * 1000000);
691  db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
692  timestamp + " WHERE fieldname = '" + name + "'");
693 
694  is(formhist.nameExists(name), true,
695  "Sanity check: form entry " + name + " should exist after creating it");
696  return name;
697 }
698 
705 function addHistoryWithMinutesAgo(aMinutesAgo) {
706  let pURI = makeURI("http://" + aMinutesAgo + "-minutes-ago.com/");
707  bhist.addPageWithDetails(pURI,
708  aMinutesAgo + " minutes ago",
709  now_uSec - (aMinutesAgo * 60 * 1000 * 1000));
710  is(bhist.isVisited(pURI), true,
711  "Sanity check: history visit " + pURI.spec +
712  " should exist after creating it");
713  return pURI;
714 }
715 
719 function blankSlate() {
720  bhist.removeAllPages();
721  dm.cleanUp();
722  formhist.removeAllEntries();
723 }
724 
735 function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
736  is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
737 }
738 
746 function downloadExists(aID)
747 {
748  let db = dm.DBConnection;
749  let stmt = db.createStatement(
750  "SELECT * " +
751  "FROM moz_downloads " +
752  "WHERE id = :id"
753  );
754  stmt.params.id = aID;
755  let rows = stmt.step();
756  stmt.finalize();
757  return !!rows;
758 }
759 
764 function doNextTest() {
765  if (gAllTests.length <= gCurrTest) {
766  blankSlate();
767  finish();
768  }
769  else {
770  let ct = gCurrTest;
771  gCurrTest++;
772  gAllTests[ct]();
773  }
774 }
775 
784 function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
785  let niceStr = aShouldBeCleared ? "no longer" : "still";
786  aDownloadIDs.forEach(function (id) {
787  is(downloadExists(id), !aShouldBeCleared,
788  "download " + id + " should " + niceStr + " exist");
789  });
790 }
791 
800 function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared) {
801  let niceStr = aShouldBeCleared ? "no longer" : "still";
802  aFormEntries.forEach(function (entry) {
803  is(formhist.nameExists(entry), !aShouldBeCleared,
804  "form entry " + entry + " should " + niceStr + " exist");
805  });
806 }
807 
816 function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
817  let niceStr = aShouldBeCleared ? "no longer" : "still";
818  aURIs.forEach(function (aURI) {
819  is(bhist.isVisited(aURI), !aShouldBeCleared,
820  "history visit " + aURI.spec + " should " + niceStr + " exist");
821  });
822 }
823 
834 function intPrefIs(aPrefName, aExpectedVal, aMsg) {
835  is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
836 }
837 
839 
840 function test() {
841  blankSlate();
843  // Kick off all the tests in the gAllTests array.
844  doNextTest();
845 }
var gDownloadId
classDescription entry
Definition: FeedWriter.js:1427
Cc["@mozilla.org/moz/jssubscript-loader;1"] getService(Components.interfaces.mozIJSSubScriptLoader).loadSubScript("chrome const winWatch
const dm
const Cc
window onload
var uris
var pref
Definition: openLocation.js:44
function blankSlate()
function addDownloadWithMinutesAgo(aMinutesAgo)
var event
const bhist
getService(Ci.sbIFaceplateManager)
function test()
function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared)
var gAllTests
var loaded
BogusChannel prototype open
function makeURI(aURLSpec, aCharset)
Definition: FeedWriter.js:71
function boolPrefIs(aPrefName, aExpectedVal, aMsg)
waitForExplicitFinish()
function intPrefIs(aPrefName, aExpectedVal, aMsg)
return null
Definition: FeedWriter.js:1143
_updateDatepicker height
var gPrefService
Definition: overlay.js:34
function WindowHelper(aWin)
function downloadExists(aID)
function addFormEntryWithMinutesAgo(aMinutesAgo)
function addHistoryWithMinutesAgo(aMinutesAgo)
function doNextTest()
const formhist
return aWindow document documentElement getAttribute(aAttribute)||dimension
function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared)
const Ci
function Sanitizer()
Definition: sanitize.js:42
var hidden
observe data
Definition: FeedWriter.js:1329
_getSelectedPageStyle s i
function ensureHistoryClearedState(aURIs, aShouldBeCleared)
_updateTextAndScrollDataForFrame aData
sbDeviceFirmwareAutoCheckForUpdate prototype observe