trackEditorWidgets.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2010 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 if (typeof(Ci) == "undefined")
26  var Ci = Components.interfaces;
27 if (typeof(Cc) == "undefined")
28  var Cc = Components.classes;
29 if (typeof(Cr) == "undefined")
30  var Cr = Components.results;
31 if (typeof(Cu) == "undefined")
32  var Cu = Components.utils;
33 
34 Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
35 Cu.import("resource://app/jsmodules/sbCoverHelper.jsm");
36 Cu.import("resource://app/jsmodules/SBJobUtils.jsm");
37 Cu.import("resource://app/jsmodules/sbLibraryUtils.jsm");
38 Cu.import("resource://app/jsmodules/sbProperties.jsm");
39 Cu.import("resource://app/jsmodules/StringUtils.jsm");
40 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
41 
42 /******************************************************************************
43  *
44  * \class TrackEditorWidgetBase
45  * \brief Base wrapper for UI elements that need to observe a track editor
46  * property value
47  *
48  * To be wrapped by this function an element must have a property attribute
49  * and .value accessor.
50  *
51  * Note that to add UI to the track editor you DO NOT need to use
52  * this or other wrapper classes. Use this, use XBL, use your own script, just
53  * observe TrackEditor.state and post back user changes.
54  *
55  *****************************************************************************/
57  this._element = element;
58 
59  TrackEditor.state.addPropertyListener(this.property, this);
60 }
61 TrackEditorWidgetBase.prototype = {
62  // The DOM element that this object is responsible for
63  _element: null,
64 
65  get property() {
66  return this._element.getAttribute("property");
67  },
68 
69  get element() {
70  return this._element;
71  },
72 
73  get disabled() {
74  return this._element.disabled;
75  },
76 
77  set disabled(val) {
78  this._element.disabled = val;
79  },
80 
81  onTrackEditorPropertyChange: function TrackEditorWidgetBase_onTrackEditorPropertyChange() {
82  var value = TrackEditor.state.getPropertyValue(this.property);
83  if (value != this._element.value) {
84  this._element.value = value;
85  }
86  }
87 }
88 
89 
90 
91 
92 
93 /******************************************************************************
94  *
95  * \class TrackEditorLabel
96  * \brief Extends TrackEditorWidgetBase for label elements that should
97  * reflect propert information
98  *
99  * If the label has a property-type="label" attribute it will receive the
100  * title associated with the property attribute. Otherwise the label will
101  * receive the current editor display value for property.
102  *
103  *****************************************************************************/
104 function TrackEditorLabel(element) {
105 
106  // If requested, just show the title of the property
107  if (element.hasAttribute("property-type") &&
108  element.getAttribute("property-type") == "label") {
109 
110  var propMan = Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
111  .getService(Ci.sbIPropertyManager);
112  var propInfo = propMan.getPropertyInfo(element.getAttribute("property"));
113  element.setAttribute("value", propInfo.displayName);
114 
115  } else {
116  // Otherwise, this label should show the value of the
117  // property, so call the parent constructor
118  TrackEditorWidgetBase.call(this, element);
119  }
120 }
121 TrackEditorLabel.prototype = {
122  __proto__: TrackEditorWidgetBase.prototype
123 }
124 
125 
126 /******************************************************************************
127  *
128  * \class TrackEditorURILabel
129  * \brief Extends TrackEditorLabel for label elements containing URLs
130  *
131  * If the label has a property-type="label" attribute it will receive the
132  * title associated with the property attribute. Otherwise the label will
133  * receive the current editor display value for property.
134  *
135  *****************************************************************************/
136 function TrackEditorURILabel(element) {
137  TrackEditorLabel.call(this, element);
138 
139  this._button = document.createElement("button");
140  this._button.setAttribute("class", "goto-url");
141 
142  var self = this;
143  this._button.addEventListener("command",
144  function() { self.onButtonCommand(); }, false);
145 
146  element.parentNode.insertBefore(this._button, element.nextSibling);
147 }
148 TrackEditorURILabel.prototype = {
149  __proto__: TrackEditorLabel.prototype,
150  _ioService: Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService),
151 
152  onTrackEditorPropertyChange: function TrackEditorWidgetBase_onTrackEditorPropertyChange() {
153  var value = TrackEditor.state.getPropertyValue(this.property);
154 
155  var formattedValue = value;
156  try {
157  // reformat the URI as a native file path
158  var ioService = Cc["@mozilla.org/network/io-service;1"]
159  .getService(Ci.nsIIOService);
160  var uri = ioService.newURI(value, null, null);
161  if (uri.scheme == "file") {
162  formattedValue = uri.QueryInterface(Ci.nsIFileURL).file.path;
163  }
164  }
165  catch(e) {
166  /* it's okay, we just leave formattedValue == value in this case */
167  }
168 
169  if (formattedValue != this._element.value) {
170  this._element.value = formattedValue;
171  }
172 
173  if (value != this._button.value) {
174  this._button.value = value;
175  }
176  },
177 
178  onButtonCommand: function()
179  {
180  this.loadOrRevealURI(this._button.value);
181  },
182 
183  loadOrRevealURI: function(uriLocation)
184  {
185  var uri = this._ioService.newURI(uriLocation, null, null);
186  if (uri.scheme == "file") {
187  this.revealURI(uri);
188  }
189  else {
190  this.promptAndLoadURI(uriLocation);
191  }
192  },
193 
194  revealURI: function(uri) {
195  // Cribbed from mozilla/toolkit/mozapps/downloads/content/downloads.js
196  var f = uri.QueryInterface(Ci.nsIFileURL).file.QueryInterface(Ci.nsILocalFile);
197 
198  try {
199  // Show the directory containing the file and select the file
200  f.reveal();
201  } catch (e) {
202  // If reveal fails for some reason (e.g., it's not implemented on unix or
203  // the file doesn't exist), try using the parent if we have it.
204  let parent = f.parent.QueryInterface(Ci.nsILocalFile);
205  if (!parent)
206  return;
207 
208  try {
209  // "Double click" the parent directory to show where the file should be
210  parent.launch();
211  } catch (e) {
212  // If launch also fails (probably because it's not implemented), let the
213  // OS handler try to open the parent
214  openExternal(parent);
215  }
216  }
217  },
218 
219  promptAndLoadURI: function(uriLocation) {
220  var properties = TrackEditor.state.getEnabledProperties();
221 
222  var edits = 0;
223  for (var i = 0; i < properties.length; i++) {
224  if (TrackEditor.state.isPropertyEdited(properties[i])) {
225  edits++;
226  }
227  }
228 
229  var items = TrackEditor.state.selectedItems;
230  if (items.length == 0 || edits == 0) {
231  // No need to muck about with prompts. Just banish the window.
232  TrackEditor._browser.loadOneTab(uriLocation, null, null, null, false, false);
233  window.close();
234  return;
235  }
236 
237  // This is not a local file, so open it in a new tab and dismiss
238  // the track editor. (Prompt first.)
239 
240  var titleMessage = SBString("trackeditor.closewarning.title");
241  var dialogMessage = SBString("trackeditor.closewarning.message");
242  var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
243  .getService(Components.interfaces.nsIPromptService);
244  var flags = prompts.BUTTON_TITLE_DONT_SAVE * prompts.BUTTON_POS_0 +
245  prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1 +
246  prompts.BUTTON_TITLE_OK * prompts.BUTTON_POS_2;
247 
248  try {
249  var button = prompts.confirmEx(window, titleMessage, dialogMessage, flags,
250  null, null, null, null, {});
251  switch (button) {
252  case 0:
253  TrackEditor._browser.loadOneTab(uriLocation, null, null, null, true, false);
254  window.close();
255  case 1: return;
256  case 2:
257  TrackEditor._browser.loadOneTab(uriLocation, null, null, null, true, false);
258  TrackEditor.closeAndApply();
259  }
260  } catch(e) {
261  Components.utils.reportError(e);
262  }
263  }
264 };
265 
266 /******************************************************************************
267  *
268  * \class TrackEditorOriginLabel
269  * \brief Extends TrackEditorURILabel for the originPage property.
270  * Basically the same, except with originPage{Title, Image} for display.
271  *
272  *****************************************************************************/
274  TrackEditorURILabel.call(this, element);
275  TrackEditor.state.addPropertyListener(SBProperties.originPageTitle, this);
276 }
277 TrackEditorOriginLabel.prototype = {
278  __proto__: TrackEditorURILabel.prototype,
279 
280  onTrackEditorPropertyChange: function (property) {
281  // NB: no property value ==> all properties liable to have changed
282  if (property == SBProperties.originPage || !property) {
283  this.onTrackEditorPagePropertyChange();
284  }
285  if (property == SBProperties.originPageTitle || !property) {
286  this.onTrackEditorPageTitlePropertyChange();
287  }
288  if (property == SBProperties.originPageImage || !property) {
289  this.onTrackEditorPageImagePropertyChange();
290  }
291  },
292 
293  onTrackEditorPagePropertyChange: function() {
294  var value = TrackEditor.state.getPropertyValue(this.property);
295 
296  var formattedValue = value;
297  try {
298  // reformat the URI as a native file path
299  var ioService = Cc["@mozilla.org/network/io-service;1"]
300  .getService(Ci.nsIIOService);
301  var uri = ioService.newURI(value, null, null);
302  if (uri.scheme == "file") {
303  formattedValue = uri.QueryInterface(Ci.nsIFileURL).file.path;
304  }
305  }
306  catch(e) {
307  /* it's okay, we just leave formattedValue == value in this case */
308  }
309 
310  if (value != this._button.value) {
311  this._button.value = value;
312  }
313  },
314 
315  onTrackEditorPageTitlePropertyChange: function() {
316  var title = TrackEditor.state.getPropertyValue(SBProperties.originPageTitle);
317  if (title != this._element.title) {
318  this._element.value = title;
319  }
320  },
321 
322  onTrackEditorPageImagePropertyChange: function() {
323  // TODO: this
324  }
325 }
326 
327 /******************************************************************************
328  *
329  * \class TrackEditorInputWidget
330  * \brief Extends TrackEditorWidgetBase to provide a base for widgets that
331  * need to read from AND write back to the track editor state.
332  *
333  * Wraps the given element to manage the readonly attribute based on
334  * track editor state and a checkbox. Synchronizes readonly state between
335  * all input elements for a given property.
336  *
337  * Assumes widgets with .value and .readonly accessors and a
338  * property attribute.
339  *
340  *****************************************************************************/
341 function TrackEditorInputWidget(element) {
342 
343  // Otherwise, this label should show the value of the
344  // property, so call the parent constructor
345  TrackEditorWidgetBase.call(this, element);
346 
347  TrackEditor.state.addSelectionListener(this);
348 
349  // Create preceding checkbox to enable/disable when
350  // multiple tracks are selected
351  this._createCheckbox();
352 }
353 TrackEditorInputWidget.prototype = {
354  __proto__: TrackEditorWidgetBase.prototype,
355 
356  // Checkbox used to enable/disable the input widget
357  // when multiple tracks are selected
358  _checkbox: null,
359 
360  _createCheckbox: function() {
361  var hbox = document.createElement("hbox");
362  this._element.parentNode.replaceChild(hbox, this._element);
363  var flex = this._element.getAttribute("flex");
364  this._checkbox = document.createElement("checkbox");
365 
366  // In order for tabbing to work in the desired order
367  // we need to apply tabindex to all elements.
368  if (this._element.hasAttribute("tabindex")) {
369  var value = parseInt(this._element.getAttribute("tabindex")) - 1;
370  this._checkbox.setAttribute("tabindex", value);
371  }
372  this._checkbox.hidden = true;
373 
374  var self = this;
375  this._checkbox.addEventListener("command",
376  function() { self.onCheckboxCommand(); }, false);
377 
378  hbox.appendChild(this._checkbox);
379  if (flex) {
380  hbox.setAttribute("flex", flex);
381  }
382  hbox.appendChild(this._element);
383  },
384 
385  set disabled(val) {
386  this._checkbox.disabled = val;
387  this._element.disabled = val;
388  },
389 
390  get hidden() {
391  return this._element.parentNode.hidden;
392  },
393 
394  set hidden(val) {
395  this._element.parentNode.hidden = val;
396  },
397 
398  onCheckboxCommand: function() {
399  TrackEditor.state.setPropertyEnabled(this.property, this._checkbox.checked);
400  if (this._checkbox.checked) {
401  this._elementStack.focus();
402  }
403  },
404 
405  onTrackEditorSelectionChange: function() {
406  this._checkbox.hidden = TrackEditor.state.selectedItems.length <= 1;
407 
408  // If none of the selected items can be modified, disable all editing
409  if (TrackEditor.state.isDisabled) {
410  this._checkbox.disabled = true;
411  this._element.setAttribute("readonly", "true");
412  this._element.setAttribute("tooltiptext",
413  SBString("trackeditor.tooltip.readonly"));
414  } else {
415  this._checkbox.disabled = false;
416  this._element.disabled = false;
417  this._element.removeAttribute("readonly");
418  this._element.removeAttribute("tooltiptext");
419  }
420  },
421 
422  onTrackEditorPropertyChange: function TrackEditorInputWidget_onTrackEditorPropertyChange() {
423  TrackEditorWidgetBase.prototype.onTrackEditorPropertyChange.call(this);
424 
425  this._checkbox.checked = TrackEditor.state.isPropertyEnabled(this.property);
426  }
427 }
428 
429 
430 /******************************************************************************
431  *
432  * \class TrackEditorTextbox
433  * \brief Extends TrackEditorInputWidget to add textbox specific details
434  *
435  * Binds the given textbox to track editor state for a property, and mananges
436  * input, updates, edited attribute, and autocomplete configuration.
437  *
438  *****************************************************************************/
439 function TrackEditorTextbox(element) {
440 
441  TrackEditorInputWidget.call(this, element);
442 
443  var self = this;
444  element.addEventListener("input",
445  function() { self.onUserInput(); }, false);
446 
447  var propMan = Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
448  .getService(Ci.sbIPropertyManager);
449  var propInfo = propMan.getPropertyInfo(this.property);
450  if (propInfo instanceof Ci.sbINumberPropertyInfo ||
451  this._element.getAttribute("isnumeric") == "true") {
452  this._isNumeric = true;
453  this._minValue = propInfo.minValue;
454  this._maxValue = propInfo.maxValue;
455  this._maxDigits = new String(this._maxValue).length;
456 
457  element.addEventListener("keypress",
458  function(evt) { self.onKeypress(evt); }, false);
459  }
460 }
461 TrackEditorTextbox.prototype = {
462  __proto__: TrackEditorInputWidget.prototype,
463 
464  // If set true, filter out non-numeric input
465  _isNumeric: false,
466  _minValue: 0,
467  _maxValue: 0,
468  _maxDigits: 0,
469 
470  onUserInput: function() {
471  var self = this;
472  // Defer updating the model by a tick. This is necessary because
473  // html:input elements (inside of textbox) send oninput too early
474  // when undoing from empty state to a previous value. html:textarea
475  // sends oninput with value==previous, where html:input sends oninput
476  // with value=="". This bug has been filed as BMO 433574.
477  setTimeout(function() {
478  var value = self._element.value;
479  TrackEditor.state.setPropertyValue(self.property, value);
480 
481  // Auto-enable property write-back
482  if (!TrackEditor.state.isPropertyEnabled(self.property)) {
483  TrackEditor.state.setPropertyEnabled(self.property, true);
484  }
485  }, 0 );
486  },
487 
488  onKeypress: function(evt) {
489  // If numeric, suppress all other keys.
490  // TODO/NOTE: THIS DOES NOT WORK FOR NEGATIVE VALUES!
491  if (this._isNumeric) {
492  if (!evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.charCode) {
493  if (evt.charCode < 48 || evt.charCode > 57) {
494  evt.preventDefault();
495  // If typing the key would make the value too long, prevent keypress
496  } else if (this._element.value.length + 1 > this._maxDigits &&
497  this._element.selectionStart == this._element.selectionEnd) {
498  evt.preventDefault();
499  }
500  }
501  }
502  },
503 
504  onTrackEditorSelectionChange: function TrackEditorTextbox_onTrackEditorSelectionChange() {
505  TrackEditorInputWidget.prototype.onTrackEditorSelectionChange.call(this);
506 
507  if (this._element.getAttribute("type") == "autocomplete") {
508  this._configureAutoComplete();
509  }
510  },
511 
512  onTrackEditorPropertyChange: function TrackEditorTextbox_onTrackEditorPropertyChange() {
513  TrackEditorInputWidget.prototype.onTrackEditorPropertyChange.call(this);
514 
515  var property = this.property;
516 
517  // Set a tooltip text for differing multi-value textboxes.
518  if (TrackEditor.state.hasMultipleValuesForProperty(this.property)) {
519  this._element.setAttribute("tooltiptext", SBString("trackeditor.tooltip.multiple"));
520  }
521  else if (this._element.getAttribute("tooltiptext") ==
522  SBString("trackeditor.tooltip.multiple")) {
523  this._element.removeAttribute("tooltiptext");
524  }
525 
526  // Indicate if this property has been edited
527  if (TrackEditor.state.isPropertyEdited(this.property)) {
528  if (!this._element.hasAttribute("edited")) {
529  this._element.setAttribute("edited", "true");
530  }
531  } else {
532  if (this._element.hasAttribute("edited")) {
533  this._element.removeAttribute("edited");
534  }
535 
536  // If this is the original un-edited value, set it as the
537  // default for the textbox and reset the undo history
538  var value = TrackEditor.state.getPropertyValue(property);
539  if (this._element.defaultValue != value) {
540  this._element.defaultValue = value;
541  this._element.reset();
542  }
543  }
544 
545  // Indicate if this property is known to be invalid
546  if (TrackEditor.state.isPropertyInvalidated(this.property)) {
547  if (!this._element.hasAttribute("invalid")) {
548  this._element.setAttribute("invalid", "true");
549  this._element.setAttribute("tooltiptext", SBString("trackeditor.tooltip.invalid"));
550  }
551  } else if (this._element.hasAttribute("invalid")) {
552  this._element.removeAttribute("invalid");
553  this._element.removeAttribute("tooltiptext");
554  }
555  },
556 
557  _configureAutoComplete: function TrackEditorTextbox__configureAutoComplete() {
558  // Set the autocompletesearchparam attribute of the
559  // autocomplete textbox to 'property;guid', where
560  // property is the mediaitem property that the textbox
561  // edits, and guid is the guid of the currently displayed
562  // library. We could omit the guid and set only the property
563  // in the xul, but the suggestions would then come
564  // from all the libraries in the system instead of only
565  // the one whom the displayed list belongs to.
566  // In addition, textboxes that have a defaultdistinctproperties
567  // attribute need to have that value appended to the
568  // search param attribute as well.
569  if (!TrackEditor.mediaListView)
570  return;
571  var library = TrackEditor.mediaListView.mediaList.library;
572  if (!library)
573  return;
574  var libraryGuid = library.guid;
575 
576  // verify that this is an autocomplete textbox, and that
577  // it belongs to us: avoid changing the param for a
578  // textbox that autocompletes to something else than
579  // distinct props. Note that we look for the search id
580  // anywhere in the attribute, because we could be getting
581  // suggestions from multiple suggesters
582  if (this._element.getAttribute("autocompletesearch")
583  .indexOf("library-distinct-properties") >= 0) {
584  var defvals = this._element.getAttribute("defaultdistinctproperties");
585  var param = this.property;
586 
587  // Get content type for genre property and set to auto complete param.
588  if (this.property == SBProperties.genre) {
589  var LSP = Cc["@songbirdnest.com/servicepane/library;1"]
590  .getService(Ci.sbILibraryServicePaneService);
591  var type =
592  LSP.getNodeContentTypeFromMediaListView(TrackEditor.mediaListView);
593  if (type)
594  param += "$" + type;
595  }
596  param += ";" + libraryGuid;
597  if (defvals && defvals != "") {
598  param += ";" + defvals;
599  }
600  this._element.setAttribute("autocompletesearchparam", param);
601  }
602 
603  // Grr, autocomplete textboxes don't handle tabindex, so we have to
604  // get our hands dirty. Filed as Moz Bug 432886.
605  this._element.inputField.tabIndex = this._element.tabIndex;
606 
607  // And we need to fix the dropdown not showing up...
608  // (Songbird bug 9149, same moz bug)
609  var marker = this._element
610  .ownerDocument
611  .getAnonymousElementByAttribute(this._element,
612  "anonid",
613  "historydropmarker");
614  if ("showPopup" in marker) {
615  // only hack the method if it's the same
616 
617  // here is the expected function...
618  function showPopup() {
619  var textbox = document.getBindingParent(this);
620  textbox.showHistoryPopup();
621  };
622 
623  // and we compare it with uneval to make sure the existing one is expected
624  if (uneval(showPopup) == uneval(marker.showPopup)) {
625  // the existing function looks like what we expect
626  marker.showPopup = function showPopup_hacked() {
627  var textbox = document.getBindingParent(this);
628  setTimeout(function(){
629  textbox.showHistoryPopup();
630  }, 0);
631  }
632  } else {
633  // the function does not match; this should not happen in practice
634  dump("trackEditor - autocomplete marker showPopup mismatch!\n");
635  }
636  }
637  }
638 }
639 
640 
641 
642 
643 
644 
645 
646 /******************************************************************************
647  *
648  * \class TrackEditorRating
649  * \brief Extends TrackEditorInputWidget to add rating specific details
650  *
651  * Binds the given sb-rating to track editor state for a property, and manages
652  * input, updates, and edited attribute
653  *
654  *****************************************************************************/
655 function TrackEditorRating(element) {
656 
657  TrackEditorInputWidget.call(this, element);
658 
659  var self = this;
660  element.addEventListener("input",
661  function() { self.onUserInput(); }, false);
662 }
663 
664 TrackEditorRating.prototype = {
665  __proto__: TrackEditorInputWidget.prototype,
666 
667  onUserInput: function() {
668  var value = this._element.value;
669  TrackEditor.state.setPropertyValue(this.property, value);
670 
671  // Auto-enable property write-back
672  if (!TrackEditor.state.isPropertyEnabled(this.property)) {
673  TrackEditor.state.setPropertyEnabled(this.property, true);
674  }
675  },
676 
677  onTrackEditorPropertyChange: function TrackEditorRating_onTrackEditorPropertyChange() {
678  var property = this.property;
679 
680  // Indicate if this property has been edited
681  if (TrackEditor.state.isPropertyEdited(this.property))
682  {
683  if (!this._element.hasAttribute("edited")) {
684  this._element.setAttribute("edited", "true");
685  }
686  } else if (this._element.hasAttribute("edited")) {
687  this._element.removeAttribute("edited");
688  }
689 
690  TrackEditorInputWidget.prototype.onTrackEditorPropertyChange.call(this);
691 
692  // Override the rating widget to never be disabled.
693  this._checkbox.disabled = false;
694  this._element.disabled = false;
695  this._element.removeAttribute("readonly");
696  this._element.removeAttribute("tooltiptext");
697  }
698 }
699 
const Cu
function TrackEditorOriginLabel(element)
const Cc
var TrackEditor
Definition: trackEditor.js:52
function TrackEditorWidgetBase(element)
sbDeviceFirmwareAutoCheckForUpdate prototype flags
function onUserInput()
var ioService
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
let window
this _dialogInput val(dateText)
aWindow setTimeout(function(){_this.restoreHistory(aWindow, aTabs, aTabData, aIdMap);}, 0)
return null
Definition: FeedWriter.js:1143
var uri
Definition: FeedWriter.js:1135
countRef value
Definition: FeedWriter.js:1423
const Cr
const Ci
var hidden
_getSelectedPageStyle s i