2018-07-26 21:57:55 +03:00
/ *
* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/.
* /
"use strict" ;
2019-01-30 00:41:48 +03:00
const { XPCOMUtils } = ChromeUtils . import ( "resource://gre/modules/XPCOMUtils.jsm" ) ;
const { Services } = ChromeUtils . import ( "resource://gre/modules/Services.jsm" ) ;
const { AppConstants } = ChromeUtils . import ( "resource://gre/modules/AppConstants.jsm" ) ;
const { E10SUtils } = ChromeUtils . import ( "resource://gre/modules/E10SUtils.jsm" ) ;
2018-07-26 21:57:55 +03:00
ChromeUtils . defineModuleGetter ( this , "AboutNewTab" ,
"resource:///modules/AboutNewTab.jsm" ) ;
const TOPIC _APP _QUIT = "quit-application-granted" ;
const TOPIC _LOCALES _CHANGE = "intl:app-locales-changed" ;
const TOPIC _CONTENT _DOCUMENT _INTERACTIVE = "content-document-interactive" ;
// Automated tests ensure packaged locales are in this list. Copied output of:
// https://github.com/mozilla/activity-stream/blob/master/bin/render-activity-stream-html.js
2018-09-28 20:46:27 +03:00
const ACTIVITY _STREAM _BCP47 = "en-US ach an ar ast az be bg bn-BD bn-IN br bs ca cak crh cs cy da de dsb el en-CA en-GB eo es-AR es-CL es-ES es-MX et eu fa ff fi fr fy-NL ga-IE gd gl gn gu-IN he hi-IN hr hsb hu hy-AM ia id is it ja ja-JP-macos ka kab kk km kn ko lij lo lt ltg lv mai mk ml mr ms my nb-NO ne-NP nl nn-NO oc pa-IN pl pt-BR pt-PT rm ro ru si sk sl sq sr sv-SE ta te th tl tr uk ur uz vi zh-CN zh-TW" . split ( " " ) ;
2018-07-26 21:57:55 +03:00
const ABOUT _URL = "about:newtab" ;
const BASE _URL = "resource://activity-stream/" ;
const ACTIVITY _STREAM _PAGES = new Set ( [ "home" , "newtab" , "welcome" ] ) ;
const IS _MAIN _PROCESS = Services . appinfo . processType === Services . appinfo . PROCESS _TYPE _DEFAULT ;
const IS _PRIVILEGED _PROCESS = Services . appinfo . remoteType === E10SUtils . PRIVILEGED _REMOTE _TYPE ;
const IS _RELEASE _OR _BETA = AppConstants . RELEASE _OR _BETA ;
const PREF _SEPARATE _PRIVILEGED _CONTENT _PROCESS = "browser.tabs.remote.separatePrivilegedContentProcess" ;
const PREF _ACTIVITY _STREAM _PRERENDER _ENABLED = "browser.newtabpage.activity-stream.prerender" ;
const PREF _ACTIVITY _STREAM _DEBUG = "browser.newtabpage.activity-stream.debug" ;
function AboutNewTabService ( ) {
Services . obs . addObserver ( this , TOPIC _APP _QUIT ) ;
Services . obs . addObserver ( this , TOPIC _LOCALES _CHANGE ) ;
Services . prefs . addObserver ( PREF _SEPARATE _PRIVILEGED _CONTENT _PROCESS , this ) ;
Services . prefs . addObserver ( PREF _ACTIVITY _STREAM _PRERENDER _ENABLED , this ) ;
if ( ! IS _RELEASE _OR _BETA ) {
Services . prefs . addObserver ( PREF _ACTIVITY _STREAM _DEBUG , this ) ;
}
// More initialization happens here
this . toggleActivityStream ( true ) ;
this . initialized = true ;
2019-02-05 19:43:24 +03:00
this . alreadyRecordedTopsitesPainted = false ;
2018-07-26 21:57:55 +03:00
if ( IS _MAIN _PROCESS ) {
AboutNewTab . init ( ) ;
} else if ( IS _PRIVILEGED _PROCESS ) {
Services . obs . addObserver ( this , TOPIC _CONTENT _DOCUMENT _INTERACTIVE ) ;
}
}
/ *
* A service that allows for the overriding , at runtime , of the newtab page ' s url .
*
* There is tight coupling with browser / about / AboutRedirector . cpp .
*
* 1. Browser chrome access :
*
* When the user issues a command to open a new tab page , usually clicking a button
* in the browser chrome or using shortcut keys , the browser chrome code invokes the
* service to obtain the newtab URL . It then loads that URL in a new tab .
*
* When not overridden , the default URL emitted by the service is "about:newtab" .
* When overridden , it returns the overriden URL .
*
* 2. Redirector Access :
*
* When the URL loaded is about : newtab , the default behavior , or when entered in the
* URL bar , the redirector is hit . The service is then called to return the
* appropriate activity stream url based on prefs and locales .
*
* NOTE : "about:newtab" will always result in a default newtab page , and never an overridden URL .
*
* Access patterns :
*
* The behavior is different when accessing the service via browser chrome or via redirector
* largely to maintain compatibility with expectations of add - on developers .
*
* Loading a chrome resource , or an about : URL in the redirector with either the
* LOAD _NORMAL or LOAD _REPLACE flags yield unexpected behaviors , so a roundtrip
* to the redirector from browser chrome is avoided .
* /
AboutNewTabService . prototype = {
_newTabURL : ABOUT _URL ,
_activityStreamEnabled : false ,
_activityStreamPrerender : false ,
_activityStreamPath : "" ,
_activityStreamDebug : false ,
_privilegedContentProcess : false ,
_overridden : false ,
willNotifyUser : false ,
classID : Components . ID ( "{dfcd2adc-7867-4d3a-ba70-17501f208142}" ) ,
QueryInterface : ChromeUtils . generateQI ( [
Ci . nsIAboutNewTabService ,
2018-09-14 23:18:00 +03:00
Ci . nsIObserver ,
2018-07-26 21:57:55 +03:00
] ) ,
observe ( subject , topic , data ) {
switch ( topic ) {
case "nsPref:changed" :
if ( data === PREF _SEPARATE _PRIVILEGED _CONTENT _PROCESS ) {
this . _privilegedContentProcess = Services . prefs . getBoolPref ( PREF _SEPARATE _PRIVILEGED _CONTENT _PROCESS ) ;
2018-11-27 21:59:08 +03:00
this . updatePrerenderedPath ( ) ;
2018-11-01 21:01:21 +03:00
this . notifyChange ( ) ;
2018-07-26 21:57:55 +03:00
} else if ( data === PREF _ACTIVITY _STREAM _PRERENDER _ENABLED ) {
this . _activityStreamPrerender = Services . prefs . getBoolPref ( PREF _ACTIVITY _STREAM _PRERENDER _ENABLED ) ;
this . notifyChange ( ) ;
} else if ( ! IS _RELEASE _OR _BETA && data === PREF _ACTIVITY _STREAM _DEBUG ) {
this . _activityStreamDebug = Services . prefs . getBoolPref ( PREF _ACTIVITY _STREAM _DEBUG , false ) ;
this . updatePrerenderedPath ( ) ;
this . notifyChange ( ) ;
}
break ;
case TOPIC _CONTENT _DOCUMENT _INTERACTIVE : {
const win = subject . defaultView ;
// It seems like "content-document-interactive" is triggered multiple
// times for a single window. The first event always seems to be an
// HTMLDocument object that contains a non-null window reference
// whereas the remaining ones seem to be proxied objects.
// https://searchfox.org/mozilla-central/rev/d2966246905102b36ef5221b0e3cbccf7ea15a86/devtools/server/actors/object.js#100-102
if ( win === null ) {
break ;
}
// We use win.location.pathname instead of win.location.toString()
// because we want to account for URLs that contain the location hash
// property or query strings (e.g. about:newtab#foo, about:home?bar).
// Asserting here would be ideal, but this code path is also taken
// by the view-source:// scheme, so we should probably just bail out
// and do nothing.
if ( ! ACTIVITY _STREAM _PAGES . has ( win . location . pathname ) ) {
break ;
}
const onLoaded = ( ) => {
const debugString = this . _activityStreamDebug ? "-dev" : "" ;
// This list must match any similar ones in render-activity-stream-html.js.
const scripts = [
"chrome://browser/content/contentSearchUI.js" ,
"chrome://browser/content/contentTheme.js" ,
` ${ BASE _URL } vendor/react ${ debugString } .js ` ,
` ${ BASE _URL } vendor/react-dom ${ debugString } .js ` ,
` ${ BASE _URL } vendor/prop-types.js ` ,
` ${ BASE _URL } vendor/react-intl.js ` ,
` ${ BASE _URL } vendor/redux.js ` ,
` ${ BASE _URL } vendor/react-redux.js ` ,
` ${ BASE _URL } prerendered/ ${ this . activityStreamLocale } /activity-stream-strings.js ` ,
2018-09-14 23:18:00 +03:00
` ${ BASE _URL } data/content/activity-stream.bundle.js ` ,
2018-07-26 21:57:55 +03:00
] ;
if ( this . _activityStreamPrerender ) {
scripts . unshift ( ` ${ BASE _URL } prerendered/static/activity-stream-initial-state.js ` ) ;
}
for ( let script of scripts ) {
2018-12-20 18:35:32 +03:00
Services . scriptloader . loadSubScript ( script , win ) ; // Synchronous call
2018-07-26 21:57:55 +03:00
}
} ;
subject . addEventListener ( "DOMContentLoaded" , onLoaded , { once : true } ) ;
// There is a possibility that DOMContentLoaded won't be fired. This
// unload event (which cannot be cancelled) will attempt to remove
// the listener for the DOMContentLoaded event.
const onUnloaded = ( ) => {
subject . removeEventListener ( "DOMContentLoaded" , onLoaded ) ;
} ;
subject . addEventListener ( "unload" , onUnloaded , { once : true } ) ;
break ;
}
case TOPIC _APP _QUIT :
this . uninit ( ) ;
if ( IS _MAIN _PROCESS ) {
AboutNewTab . uninit ( ) ;
} else if ( IS _PRIVILEGED _PROCESS ) {
Services . obs . removeObserver ( this , TOPIC _CONTENT _DOCUMENT _INTERACTIVE ) ;
}
break ;
case TOPIC _LOCALES _CHANGE :
this . updatePrerenderedPath ( ) ;
this . notifyChange ( ) ;
break ;
}
} ,
notifyChange ( ) {
Services . obs . notifyObservers ( null , "newtab-url-changed" , this . _newTabURL ) ;
} ,
/ * *
* React to changes to the activity stream being enabled or not .
*
* This will only act if there is a change of state and if not overridden .
*
* @ returns { Boolean } Returns if there has been a state change
*
* @ param { Boolean } stateEnabled activity stream enabled state to set to
* @ param { Boolean } forceState force state change
* /
toggleActivityStream ( stateEnabled , forceState = false ) {
if ( ! forceState && ( this . overridden || stateEnabled === this . activityStreamEnabled ) ) {
// exit there is no change of state
return false ;
}
if ( stateEnabled ) {
this . _activityStreamEnabled = true ;
} else {
this . _activityStreamEnabled = false ;
}
this . _privilegedContentProcess = Services . prefs . getBoolPref ( PREF _SEPARATE _PRIVILEGED _CONTENT _PROCESS ) ;
this . _activityStreamPrerender = Services . prefs . getBoolPref ( PREF _ACTIVITY _STREAM _PRERENDER _ENABLED ) ;
if ( ! IS _RELEASE _OR _BETA ) {
this . _activityStreamDebug = Services . prefs . getBoolPref ( PREF _ACTIVITY _STREAM _DEBUG , false ) ;
}
this . updatePrerenderedPath ( ) ;
this . _newtabURL = ABOUT _URL ;
return true ;
} ,
/ * *
* Figure out what path under prerendered to use based on current state .
* /
updatePrerenderedPath ( ) {
2018-11-27 21:59:08 +03:00
// Debug files are specially packaged in a non-localized directory, but with
// dynamic script loading, localized debug is supported.
this . _activityStreamPath = ` ${ this . _activityStreamDebug &&
! this . _privilegedContentProcess ? "static" : this . activityStreamLocale } / ` ;
2018-07-26 21:57:55 +03:00
} ,
/ *
* Returns the default URL .
*
* This URL depends on various activity stream prefs and locales . Overriding
* the newtab page has no effect on the result of this function .
* /
get defaultURL ( ) {
// Generate the desired activity stream resource depending on state, e.g.,
// resource://activity-stream/prerendered/ar/activity-stream.html
// resource://activity-stream/prerendered/en-US/activity-stream-prerendered.html
// resource://activity-stream/prerendered/static/activity-stream-debug.html
return [
"resource://activity-stream/prerendered/" ,
this . _activityStreamPath ,
"activity-stream" ,
this . _activityStreamPrerender ? "-prerendered" : "" ,
2018-11-27 21:59:08 +03:00
// Debug version loads dev scripts but noscripts separately loads scripts
this . _activityStreamDebug && ! this . _privilegedContentProcess ? "-debug" : "" ,
2018-07-26 21:57:55 +03:00
this . _privilegedContentProcess ? "-noscripts" : "" ,
2018-09-14 23:18:00 +03:00
".html" ,
2018-07-26 21:57:55 +03:00
] . join ( "" ) ;
} ,
/ *
* Returns the about : welcome URL
*
* This is calculated in the same way the default URL is , except that we don ' t
* allow prerendering .
* /
get welcomeURL ( ) {
const prerenderEnabled = this . _activityStreamPrerender ;
this . _activityStreamPrerender = false ;
const url = this . defaultURL ;
this . _activityStreamPrerender = prerenderEnabled ;
return url ;
} ,
get newTabURL ( ) {
return this . _newTabURL ;
} ,
set newTabURL ( aNewTabURL ) {
let newTabURL = aNewTabURL . trim ( ) ;
if ( newTabURL === ABOUT _URL ) {
// avoid infinite redirects in case one sets the URL to about:newtab
this . resetNewTabURL ( ) ;
return ;
} else if ( newTabURL === "" ) {
newTabURL = "about:blank" ;
}
this . toggleActivityStream ( false ) ;
this . _newTabURL = newTabURL ;
this . _overridden = true ;
this . notifyChange ( ) ;
} ,
get overridden ( ) {
return this . _overridden ;
} ,
get activityStreamEnabled ( ) {
return this . _activityStreamEnabled ;
} ,
get activityStreamPrerender ( ) {
return this . _activityStreamPrerender ;
} ,
get activityStreamDebug ( ) {
return this . _activityStreamDebug ;
} ,
get activityStreamLocale ( ) {
// Pick the best available locale to match the app locales
return Services . locale . negotiateLanguages (
2018-07-31 20:21:45 +03:00
// Fix up incorrect BCP47 that are actually lang tags as a workaround for
// bug 1479606 returning the wrong values in the content process
2018-09-24 20:53:01 +03:00
Services . locale . appLocalesAsBCP47 . map ( l => l . replace ( /^(ja-JP-mac)$/ , "$1os" ) ) ,
2018-07-27 22:58:59 +03:00
ACTIVITY _STREAM _BCP47 ,
2018-07-26 21:57:55 +03:00
// defaultLocale's strings aren't necessarily packaged, but en-US' are
2018-07-27 22:58:59 +03:00
"en-US" ,
Services . locale . langNegStrategyLookup
// Convert the BCP47 to lang tag, which is what is used in our paths, as a
// workaround for bug 1478930 negotiating incorrectly with lang tags
) [ 0 ] . replace ( /^(ja-JP-mac)os$/ , "$1" ) ;
2018-07-26 21:57:55 +03:00
} ,
resetNewTabURL ( ) {
this . _overridden = false ;
this . _newTabURL = ABOUT _URL ;
this . toggleActivityStream ( true , true ) ;
this . notifyChange ( ) ;
} ,
2019-02-05 19:43:24 +03:00
maybeRecordTopsitesPainted ( timestamp ) {
if ( this . alreadyRecordedTopsitesPainted ) {
return ;
}
const SCALAR _KEY = "timestamps.about_home_topsites_first_paint" ;
let startupInfo = Services . startup . getStartupInfo ( ) ;
let processStartTs = startupInfo . process . getTime ( ) ;
let delta = Math . round ( timestamp - processStartTs ) ;
Services . telemetry . scalarSet ( SCALAR _KEY , delta ) ;
this . alreadyRecordedTopsitesPainted = true ;
} ,
2018-07-26 21:57:55 +03:00
uninit ( ) {
if ( ! this . initialized ) {
return ;
}
Services . obs . removeObserver ( this , TOPIC _APP _QUIT ) ;
Services . obs . removeObserver ( this , TOPIC _LOCALES _CHANGE ) ;
Services . prefs . removeObserver ( PREF _SEPARATE _PRIVILEGED _CONTENT _PROCESS , this ) ;
Services . prefs . removeObserver ( PREF _ACTIVITY _STREAM _PRERENDER _ENABLED , this ) ;
if ( ! IS _RELEASE _OR _BETA ) {
Services . prefs . removeObserver ( PREF _ACTIVITY _STREAM _DEBUG , this ) ;
}
this . initialized = false ;
2018-09-14 23:18:00 +03:00
} ,
2018-07-26 21:57:55 +03:00
} ;
this . NSGetFactory = XPCOMUtils . generateNSGetFactory ( [ AboutNewTabService ] ) ;