mobile/android/chrome/content/aboutReader.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
     7 Cu.import("resource://gre/modules/Services.jsm")
     8 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    10 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
    11                                   "resource://gre/modules/UITelemetry.jsm");
    13 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
    14   window.QueryInterface(Ci.nsIInterfaceRequestor)
    15     .getInterface(Ci.nsIWebNavigation)
    16     .QueryInterface(Ci.nsIDocShellTreeItem)
    17     .rootTreeItem
    18     .QueryInterface(Ci.nsIInterfaceRequestor)
    19     .getInterface(Ci.nsIDOMWindow)
    20     .QueryInterface(Ci.nsIDOMChromeWindow));
    22 function dump(s) {
    23   Services.console.logStringMessage("AboutReader: " + s);
    24 }
    26 let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutReader.properties");
    28 let AboutReader = function(doc, win) {
    29   dump("Init()");
    31   this._docRef = Cu.getWeakReference(doc);
    32   this._winRef = Cu.getWeakReference(win);
    34   Services.obs.addObserver(this, "Reader:FaviconReturn", false);
    35   Services.obs.addObserver(this, "Reader:Add", false);
    36   Services.obs.addObserver(this, "Reader:Remove", false);
    37   Services.obs.addObserver(this, "Reader:ListStatusReturn", false);
    39   this._article = null;
    41   dump("Feching toolbar, header and content notes from about:reader");
    42   this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
    43   this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
    44   this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
    45   this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
    46   this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
    47   this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
    48   this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
    50   this._toolbarEnabled = false;
    52   this._scrollOffset = win.pageYOffset;
    54   let body = doc.body;
    55   body.addEventListener("touchstart", this, false);
    56   body.addEventListener("click", this, false);
    58   win.addEventListener("unload", this, false);
    59   win.addEventListener("scroll", this, false);
    60   win.addEventListener("popstate", this, false);
    61   win.addEventListener("resize", this, false);
    63   this._setupAllDropdowns();
    64   this._setupButton("toggle-button", this._onReaderToggle.bind(this));
    65   this._setupButton("share-button", this._onShare.bind(this));
    67   let colorSchemeOptions = [
    68     { name: gStrings.GetStringFromName("aboutReader.colorSchemeDark"),
    69       value: "dark"},
    70     { name: gStrings.GetStringFromName("aboutReader.colorSchemeLight"),
    71       value: "light"},
    72     { name: gStrings.GetStringFromName("aboutReader.colorSchemeAuto"),
    73       value: "auto"}
    74   ];
    76   let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
    77   this._setupSegmentedButton("color-scheme-buttons", colorSchemeOptions, colorScheme, this._setColorSchemePref.bind(this));
    78   this._setColorSchemePref(colorScheme);
    80   let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
    81   let fontTypeOptions = [
    82     { name: fontTypeSample,
    83       description: gStrings.GetStringFromName("aboutReader.fontTypeSerif"),
    84       value: "serif",
    85       linkClass: "serif" },
    86     { name: fontTypeSample,
    87       description: gStrings.GetStringFromName("aboutReader.fontTypeSansSerif"),
    88       value: "sans-serif",
    89       linkClass: "sans-serif"
    90     },
    91   ];
    93   let fontType = Services.prefs.getCharPref("reader.font_type");
    94   this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
    95   this._setFontType(fontType);
    97   let fontSizeSample = gStrings.GetStringFromName("aboutReader.fontSizeSample");
    98   let fontSizeOptions = [
    99     { name: fontSizeSample,
   100       value: 1,
   101       linkClass: "font-size1-sample" },
   102     { name: fontSizeSample,
   103       value: 2,
   104       linkClass: "font-size2-sample" },
   105     { name: fontSizeSample,
   106       value: 3,
   107       linkClass: "font-size3-sample" },
   108     { name: fontSizeSample,
   109       value: 4,
   110       linkClass: "font-size4-sample" },
   111     { name: fontSizeSample,
   112       value: 5,
   113       linkClass: "font-size5-sample" }
   114   ];
   116   let fontSize = Services.prefs.getIntPref("reader.font_size");
   117   this._setupSegmentedButton("font-size-buttons", fontSizeOptions, fontSize, this._setFontSize.bind(this));
   118   this._setFontSize(fontSize);
   120   dump("Decoding query arguments");
   121   let queryArgs = this._decodeQueryString(win.location.href);
   123   // Track status of reader toolbar add/remove toggle button
   124   this._isReadingListItem = -1;
   125   this._updateToggleButton();
   127   let url = queryArgs.url;
   128   let tabId = queryArgs.tabId;
   129   if (tabId) {
   130     dump("Loading from tab with ID: " + tabId + ", URL: " + url);
   131     this._loadFromTab(tabId, url);
   132   } else {
   133     dump("Fetching page with URL: " + url);
   134     this._loadFromURL(url);
   135   }
   136 }
   138 AboutReader.prototype = {
   139   _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
   140                           ".content p > a:only-child > img:only-child, " +
   141                           ".content .wp-caption img, " +
   142                           ".content figure img",
   144   get _doc() {
   145     return this._docRef.get();
   146   },
   148   get _win() {
   149     return this._winRef.get();
   150   },
   152   get _headerElement() {
   153     return this._headerElementRef.get();
   154   },
   156   get _domainElement() {
   157     return this._domainElementRef.get();
   158   },
   160   get _titleElement() {
   161     return this._titleElementRef.get();
   162   },
   164   get _creditsElement() {
   165     return this._creditsElementRef.get();
   166   },
   168   get _contentElement() {
   169     return this._contentElementRef.get();
   170   },
   172   get _toolbarElement() {
   173     return this._toolbarElementRef.get();
   174   },
   176   get _messageElement() {
   177     return this._messageElementRef.get();
   178   },
   180   observe: function Reader_observe(aMessage, aTopic, aData) {
   181     switch(aTopic) {
   182       case "Reader:FaviconReturn": {
   183         let args = JSON.parse(aData);
   184         this._loadFavicon(args.url, args.faviconUrl);
   185         Services.obs.removeObserver(this, "Reader:FaviconReturn");
   186         break;
   187       }
   189       case "Reader:Add": {
   190         let args = JSON.parse(aData);
   191         if (args.url == this._article.url) {
   192           if (this._isReadingListItem != 1) {
   193             this._isReadingListItem = 1;
   194             this._updateToggleButton();
   195           }
   196         }
   197         break;
   198       }
   200       case "Reader:Remove": {
   201         if (aData == this._article.url) {
   202           if (this._isReadingListItem != 0) {
   203             this._isReadingListItem = 0;
   204             this._updateToggleButton();
   205           }
   206         }
   207         break;
   208       }
   210       case "Reader:ListStatusReturn": {
   211         let args = JSON.parse(aData);
   212         if (args.url == this._article.url) {
   213           if (this._isReadingListItem != args.inReadingList) {
   214             let isInitialStateChange = (this._isReadingListItem == -1);
   215             this._isReadingListItem = args.inReadingList;
   216             this._updateToggleButton();
   218             // Display the toolbar when all its initial component states are known
   219             if (isInitialStateChange) {
   220               this._setToolbarVisibility(true);
   221             }
   222           }
   223         }
   224         break;
   225       }
   226     }
   227   },
   229   handleEvent: function Reader_handleEvent(aEvent) {
   230     if (!aEvent.isTrusted)
   231       return;
   233     switch (aEvent.type) {
   234       case "touchstart":
   235         this._scrolled = false;
   236         break;
   237       case "click":
   238         if (!this._scrolled)
   239           this._toggleToolbarVisibility();
   240         break;
   241       case "scroll":
   242         if (!this._scrolled) {
   243           let isScrollingUp = this._scrollOffset > aEvent.pageY;
   244           this._setToolbarVisibility(isScrollingUp);
   245           this._scrollOffset = aEvent.pageY;
   246         }
   247         break;
   248       case "popstate":
   249         if (!aEvent.state)
   250           this._closeAllDropdowns();
   251         break;
   252       case "resize":
   253         this._updateImageMargins();
   254         break;
   256       case "devicelight":
   257         this._handleDeviceLight(aEvent.value);
   258         break;
   260       case "unload":
   261         Services.obs.removeObserver(this, "Reader:Add");
   262         Services.obs.removeObserver(this, "Reader:Remove");
   263         Services.obs.removeObserver(this, "Reader:ListStatusReturn");
   264         break;
   265     }
   266   },
   268   _updateToggleButton: function Reader_updateToggleButton() {
   269     let classes = this._doc.getElementById("toggle-button").classList;
   271     if (this._isReadingListItem == 1) {
   272       classes.add("on");
   273     } else {
   274       classes.remove("on");
   275     }
   276   },
   278   _requestReadingListStatus: function Reader_requestReadingListStatus() {
   279     gChromeWin.sendMessageToJava({
   280       type: "Reader:ListStatusRequest",
   281       url: this._article.url
   282     });
   283   },
   285   _onReaderToggle: function Reader_onToggle() {
   286     if (!this._article)
   287       return;
   289     this._isReadingListItem = (this._isReadingListItem == 1) ? 0 : 1;
   290     this._updateToggleButton();
   292     // Create a relative timestamp for telemetry
   293     let uptime = Date.now() - Services.startup.getStartupInfo().linkerInitialized;
   295     if (this._isReadingListItem == 1) {
   296       gChromeWin.Reader.storeArticleInCache(this._article, function(success) {
   297         dump("Reader:Add (in reader) success=" + success);
   299         let result = gChromeWin.Reader.READER_ADD_FAILED;
   300         if (success) {
   301           result = gChromeWin.Reader.READER_ADD_SUCCESS;
   302           UITelemetry.addEvent("save.1", "button", uptime, "reader");
   303         }
   305         let json = JSON.stringify({ fromAboutReader: true, url: this._article.url });
   306         Services.obs.notifyObservers(null, "Reader:Add", json);
   308         gChromeWin.sendMessageToJava({
   309           type: "Reader:Added",
   310           result: result,
   311           title: this._article.title,
   312           url: this._article.url,
   313           length: this._article.length,
   314           excerpt: this._article.excerpt
   315         });
   316       }.bind(this));
   317     } else {
   318       // In addition to removing the article from the cache (handled in
   319       // browser.js), sending this message will cause the toggle button to be
   320       // updated (handled in this file).
   321       Services.obs.notifyObservers(null, "Reader:Remove", this._article.url);
   323       UITelemetry.addEvent("unsave.1", "button", uptime, "reader");
   324     }
   325   },
   327   _onShare: function Reader_onShare() {
   328     if (!this._article)
   329       return;
   331     gChromeWin.sendMessageToJava({
   332       type: "Reader:Share",
   333       url: this._article.url,
   334       title: this._article.title
   335     });
   337     // Create a relative timestamp for telemetry
   338     let uptime = Date.now() - Services.startup.getStartupInfo().linkerInitialized;
   339     UITelemetry.addEvent("share.1", "list", uptime);
   340   },
   342   _setFontSize: function Reader_setFontSize(newFontSize) {
   343     let bodyClasses = this._doc.body.classList;
   345     if (this._fontSize > 0)
   346       bodyClasses.remove("font-size" + this._fontSize);
   348     this._fontSize = newFontSize;
   349     bodyClasses.add("font-size" + this._fontSize);
   351     Services.prefs.setIntPref("reader.font_size", this._fontSize);
   352   },
   354   _handleDeviceLight: function Reader_handleDeviceLight(newLux) {
   355     // Desired size of the this._luxValues array.
   356     let luxValuesSize = 10;
   357     // Add new lux value at the front of the array.
   358     this._luxValues.unshift(newLux);
   359     // Add new lux value to this._totalLux for averaging later.
   360     this._totalLux += newLux;
   362     // Don't update when length of array is less than luxValuesSize except when it is 1.
   363     if (this._luxValues.length < luxValuesSize) {
   364       // Use the first lux value to set the color scheme until our array equals luxValuesSize.
   365       if (this._luxValues.length == 1) {
   366         this._updateColorScheme(newLux);
   367       }
   368       return;
   369     }
   370     // Holds the average of the lux values collected in this._luxValues.
   371     let averageLuxValue = this._totalLux/luxValuesSize;
   373     this._updateColorScheme(averageLuxValue);
   374     // Pop the oldest value off the array.
   375     let oldLux = this._luxValues.pop();
   376     // Subtract oldLux since it has been discarded from the array.
   377     this._totalLux -= oldLux;
   378   },
   380   _updateColorScheme: function Reader_updateColorScheme(luxValue) {
   381     // Upper bound value for "dark" color scheme beyond which it changes to "light".
   382     let upperBoundDark = 50;
   383     // Lower bound value for "light" color scheme beyond which it changes to "dark".
   384     let lowerBoundLight = 10;
   385     // Threshold for color scheme change.
   386     let colorChangeThreshold = 20;
   388     // Ignore changes that are within a certain threshold of previous lux values.
   389     if ((this._colorScheme === "dark" && luxValue < upperBoundDark) ||
   390         (this._colorScheme === "light" && luxValue > lowerBoundLight))
   391       return;
   393     if (luxValue < colorChangeThreshold)
   394       this._setColorScheme("dark");
   395     else
   396       this._setColorScheme("light");
   397   },
   399   _setColorScheme: function Reader_setColorScheme(newColorScheme) {
   400     if (this._colorScheme === newColorScheme)
   401       return;
   403     let bodyClasses = this._doc.body.classList;
   405     if (this._colorScheme)
   406       bodyClasses.remove(this._colorScheme);
   408     this._colorScheme = newColorScheme;
   409     bodyClasses.add(this._colorScheme);
   410   },
   412   // Pref values include "dark", "light", and "auto", which automatically switches
   413   // between light and dark color schemes based on the ambient light level.
   414   _setColorSchemePref: function Reader_setColorSchemePref(colorSchemePref) {
   415     if (colorSchemePref === "auto") {
   416       this._win.addEventListener("devicelight", this, false);
   417       this._luxValues = [];
   418       this._totalLux = 0;
   419     } else {
   420       this._win.removeEventListener("devicelight", this, false);
   421       this._setColorScheme(colorSchemePref);
   422       delete this._luxValues;
   423       delete this._totalLux;
   424     }
   426     Services.prefs.setCharPref("reader.color_scheme", colorSchemePref);
   427   },
   429   _setFontType: function Reader_setFontType(newFontType) {
   430     if (this._fontType === newFontType)
   431       return;
   433     let bodyClasses = this._doc.body.classList;
   435     if (this._fontType)
   436       bodyClasses.remove(this._fontType);
   438     this._fontType = newFontType;
   439     bodyClasses.add(this._fontType);
   441     Services.prefs.setCharPref("reader.font_type", this._fontType);
   442   },
   444   _getToolbarVisibility: function Reader_getToolbarVisibility() {
   445     return !this._toolbarElement.classList.contains("toolbar-hidden");
   446   },
   448   _setToolbarVisibility: function Reader_setToolbarVisibility(visible) {
   449     let win = this._win;
   450     if (win.history.state)
   451       win.history.back();
   453     if (!this._toolbarEnabled)
   454       return;
   456     // Don't allow visible toolbar until banner state is known
   457     if (this._isReadingListItem == -1)
   458       return;
   460     if (this._getToolbarVisibility() === visible)
   461       return;
   463     this._toolbarElement.classList.toggle("toolbar-hidden");
   464     this._setSystemUIVisibility(visible);
   466     if (!visible && !this._hasUsedToolbar) {
   467       this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
   468       if (!this._hasUsedToolbar) {
   469         gChromeWin.NativeWindow.toast.show(gStrings.GetStringFromName("aboutReader.toolbarTip"), "short");
   471         Services.prefs.setBoolPref("reader.has_used_toolbar", true);
   472         this._hasUsedToolbar = true;
   473       }
   474     }
   475   },
   477   _toggleToolbarVisibility: function Reader_toggleToolbarVisibility(visible) {
   478     this._setToolbarVisibility(!this._getToolbarVisibility());
   479   },
   481   _setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
   482     gChromeWin.sendMessageToJava({
   483       type: "SystemUI:Visibility",
   484       visible: visible
   485     });
   486   },
   488   _loadFromURL: function Reader_loadFromURL(url) {
   489     this._showProgressDelayed();
   491     gChromeWin.Reader.parseDocumentFromURL(url, function(article) {
   492       if (article)
   493         this._showContent(article);
   494       else
   495         this._showError(gStrings.GetStringFromName("aboutReader.loadError"));
   496     }.bind(this));
   497   },
   499   _loadFromTab: function Reader_loadFromTab(tabId, url) {
   500     this._showProgressDelayed();
   502     gChromeWin.Reader.getArticleForTab(tabId, url, function(article) {
   503       if (article)
   504         this._showContent(article);
   505       else
   506         this._showError(gStrings.GetStringFromName("aboutReader.loadError"));
   507     }.bind(this));
   508   },
   510   _requestFavicon: function Reader_requestFavicon() {
   511     gChromeWin.sendMessageToJava({
   512       type: "Reader:FaviconRequest",
   513       url: this._article.url
   514     });
   515   },
   517   _loadFavicon: function Reader_loadFavicon(url, faviconUrl) {
   518     if (this._article.url !== url)
   519       return;
   521     let doc = this._doc;
   523     let link = doc.createElement('link');
   524     link.rel = 'shortcut icon';
   525     link.href = faviconUrl;
   527     doc.getElementsByTagName('head')[0].appendChild(link);
   528   },
   530   _updateImageMargins: function Reader_updateImageMargins() {
   531     let windowWidth = this._win.innerWidth;
   532     let contentWidth = this._contentElement.offsetWidth;
   533     let maxWidthStyle = windowWidth + "px !important";
   535     let setImageMargins = function(img) {
   536       if (!img._originalWidth)
   537         img._originalWidth = img.offsetWidth;
   539       let imgWidth = img._originalWidth;
   541       // If the image is taking more than half of the screen, just make
   542       // it fill edge-to-edge.
   543       if (imgWidth < contentWidth && imgWidth > windowWidth * 0.55)
   544         imgWidth = windowWidth;
   546       let sideMargin = Math.max((contentWidth - windowWidth) / 2,
   547                                 (contentWidth - imgWidth) / 2);
   549       let imageStyle = sideMargin + "px !important";
   550       let widthStyle = imgWidth + "px !important";
   552       let cssText = "max-width: " + maxWidthStyle + ";" +
   553                     "width: " + widthStyle + ";" +
   554                     "margin-left: " + imageStyle + ";" +
   555                     "margin-right: " + imageStyle + ";";
   557       img.style.cssText = cssText;
   558     }
   560     let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
   561     for (let i = imgs.length; --i >= 0;) {
   562       let img = imgs[i];
   564       if (img.width > 0) {
   565         setImageMargins(img);
   566       } else {
   567         img.onload = function() {
   568           setImageMargins(img);
   569         }
   570       }
   571     }
   572   },
   574   _maybeSetTextDirection: function Read_maybeSetTextDirection(article){
   575     if(!article.dir)
   576       return;
   578     //Set "dir" attribute on content
   579     this._contentElement.setAttribute("dir", article.dir);
   580     this._headerElement.setAttribute("dir", article.dir);
   581   },
   583   _showError: function Reader_showError(error) {
   584     this._headerElement.style.display = "none";
   585     this._contentElement.style.display = "none";
   587     this._messageElement.innerHTML = error;
   588     this._messageElement.style.display = "block";
   590     this._doc.title = error;
   591   },
   593   // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
   594   _stripHost: function Reader_stripHost(host) {
   595     if (!host)
   596       return host;
   598     let start = 0;
   600     if (host.startsWith("www."))
   601       start = 4;
   602     else if (host.startsWith("m."))
   603       start = 2;
   604     else if (host.startsWith("mobile."))
   605       start = 7;
   607     return host.substring(start);
   608   },
   610   _showContent: function Reader_showContent(article) {
   611     this._messageElement.style.display = "none";
   613     this._article = article;
   615     this._domainElement.href = article.url;
   616     let articleUri = Services.io.newURI(article.url, null, null);
   617     this._domainElement.innerHTML = this._stripHost(articleUri.host);
   619     this._creditsElement.innerHTML = article.byline;
   621     this._titleElement.textContent = article.title;
   622     this._doc.title = article.title;
   624     this._headerElement.style.display = "block";
   626     let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
   627     let contentFragment = parserUtils.parseFragment(article.content, Ci.nsIParserUtils.SanitizerDropForms,
   628                                                     false, articleUri, this._contentElement);
   629     this._contentElement.innerHTML = "";
   630     this._contentElement.appendChild(contentFragment);
   631     this._updateImageMargins();
   632     this._maybeSetTextDirection(article);
   634     this._contentElement.style.display = "block";
   635     this._requestReadingListStatus();
   637     this._toolbarEnabled = true;
   638     this._setToolbarVisibility(true);
   640     this._requestFavicon();
   641   },
   643   _hideContent: function Reader_hideContent() {
   644     this._headerElement.style.display = "none";
   645     this._contentElement.style.display = "none";
   646   },
   648   _showProgressDelayed: function Reader_showProgressDelayed() {
   649     this._win.setTimeout(function() {
   650       // Article has already been loaded, no need to show
   651       // progress anymore.
   652       if (this._article)
   653         return;
   655       this._headerElement.style.display = "none";
   656       this._contentElement.style.display = "none";
   658       this._messageElement.innerHTML = gStrings.GetStringFromName("aboutReader.loading");
   659       this._messageElement.style.display = "block";
   660     }.bind(this), 300);
   661   },
   663   _decodeQueryString: function Reader_decodeQueryString(url) {
   664     let result = {};
   665     let query = url.split("?")[1];
   666     if (query) {
   667       let pairs = query.split("&");
   668       for (let i = 0; i < pairs.length; i++) {
   669         let [name, value] = pairs[i].split("=");
   670         result[name] = decodeURIComponent(value);
   671       }
   672     }
   674     return result;
   675   },
   677   _setupSegmentedButton: function Reader_setupSegmentedButton(id, options, initialValue, callback) {
   678     let doc = this._doc;
   679     let segmentedButton = doc.getElementById(id);
   681     for (let i = 0; i < options.length; i++) {
   682       let option = options[i];
   684       let item = doc.createElement("li");
   685       let link = doc.createElement("a");
   686       link.textContent = option.name;
   687       item.appendChild(link);
   689       if (option.linkClass !== undefined)
   690         link.classList.add(option.linkClass);
   692       if (option.description !== undefined) {
   693         let description = doc.createElement("div");
   694         description.textContent = option.description;
   695         item.appendChild(description);
   696       }
   698       link.style.MozUserSelect = 'none';
   699       segmentedButton.appendChild(item);
   701       link.addEventListener("click", function(aEvent) {
   702         if (!aEvent.isTrusted)
   703           return;
   705         aEvent.stopPropagation();
   707         // Create a relative timestamp for telemetry
   708         let uptime = Date.now() - Services.startup.getStartupInfo().linkerInitialized;
   709         // Just pass the ID of the button as an extra and hope the ID doesn't change
   710         // unless the context changes
   711         UITelemetry.addEvent("action.1", "button", uptime, id);
   713         let items = segmentedButton.children;
   714         for (let j = items.length - 1; j >= 0; j--) {
   715           items[j].classList.remove("selected");
   716         }
   718         item.classList.add("selected");
   719         callback(option.value);
   720       }.bind(this), true);
   722       if (option.value === initialValue)
   723         item.classList.add("selected");
   724     }
   725   },
   727   _setupButton: function Reader_setupButton(id, callback) {
   728     let button = this._doc.getElementById(id);
   730     button.addEventListener("click", function(aEvent) {
   731       if (!aEvent.isTrusted)
   732         return;
   734       aEvent.stopPropagation();
   735       callback();
   736     }, true);
   737   },
   739   _setupAllDropdowns: function Reader_setupAllDropdowns() {
   740     let doc = this._doc;
   741     let win = this._win;
   743     let dropdowns = doc.getElementsByClassName("dropdown");
   745     for (let i = dropdowns.length - 1; i >= 0; i--) {
   746       let dropdown = dropdowns[i];
   748       let dropdownToggle = dropdown.getElementsByClassName("dropdown-toggle")[0];
   749       let dropdownPopup = dropdown.getElementsByClassName("dropdown-popup")[0];
   751       if (!dropdownToggle || !dropdownPopup)
   752         continue;
   754       let dropdownArrow = doc.createElement("div");
   755       dropdownArrow.className = "dropdown-arrow";
   756       dropdownPopup.appendChild(dropdownArrow);
   758       let updatePopupPosition = function() {
   759         let popupWidth = dropdownPopup.offsetWidth + 30;
   760         let arrowWidth = dropdownArrow.offsetWidth;
   761         let toggleWidth = dropdownToggle.offsetWidth;
   762         let toggleLeft = dropdownToggle.offsetLeft;
   764         let popupShift = (toggleWidth - popupWidth) / 2;
   765         let popupLeft = Math.max(0, Math.min(win.innerWidth - popupWidth, toggleLeft + popupShift));
   766         dropdownPopup.style.left = popupLeft + "px";
   768         let arrowShift = (toggleWidth - arrowWidth) / 2;
   769         let arrowLeft = toggleLeft - popupLeft + arrowShift;
   770         dropdownArrow.style.left = arrowLeft + "px";
   771       };
   773       win.addEventListener("resize", function(aEvent) {
   774         if (!aEvent.isTrusted)
   775           return;
   777         // Wait for reflow before calculating the new position of the popup.
   778         setTimeout(updatePopupPosition, 0);
   779       }, true);
   781       dropdownToggle.addEventListener("click", function(aEvent) {
   782         if (!aEvent.isTrusted)
   783           return;
   785         aEvent.stopPropagation();
   787         if (!this._getToolbarVisibility())
   788           return;
   790         let dropdownClasses = dropdown.classList;
   792         if (dropdownClasses.contains("open")) {
   793           win.history.back();
   794         } else {
   795           updatePopupPosition();
   796           if (!this._closeAllDropdowns())
   797             this._pushDropdownState();
   799           dropdownClasses.add("open");
   800         }
   801       }.bind(this), true);
   802     }
   803   },
   805   _pushDropdownState: function Reader_pushDropdownState() {
   806     // FIXME: We're getting a NS_ERROR_UNEXPECTED error when we try
   807     // to do win.history.pushState() here (see bug 682296). This is
   808     // a workaround that allows us to push history state on the target
   809     // content document.
   811     let doc = this._doc;
   812     let body = doc.body;
   814     if (this._pushStateScript)
   815       body.removeChild(this._pushStateScript);
   817     this._pushStateScript = doc.createElement('script');
   818     this._pushStateScript.type = "text/javascript";
   819     this._pushStateScript.innerHTML = 'history.pushState({ dropdown: 1 }, document.title);';
   821     body.appendChild(this._pushStateScript);
   822   },
   824   _closeAllDropdowns : function Reader_closeAllDropdowns() {
   825     let dropdowns = this._doc.querySelectorAll(".dropdown.open");
   826     for (let i = dropdowns.length - 1; i >= 0; i--) {
   827       dropdowns[i].classList.remove("open");
   828     }
   830     return (dropdowns.length > 0)
   831   }
   832 };

mercurial