mobile/android/chrome/content/aboutReader.js

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial