diff options
Diffstat (limited to 'mobile/android/chrome/content')
52 files changed, 0 insertions, 15848 deletions
diff --git a/mobile/android/chrome/content/.eslintrc b/mobile/android/chrome/content/.eslintrc deleted file mode 100644 index 32513189a..000000000 --- a/mobile/android/chrome/content/.eslintrc +++ /dev/null @@ -1,23 +0,0 @@ -globals: - # TODO: Maybe this should be by file - BrowserApp: false - Cc: false - Ci: false - Cu: false - NativeWindow: false - PageActions: false - ReaderMode: false - SimpleServiceDiscovery: false - TabMirror: false - MediaPlayerApp: false - RokuApp: false - SearchEngines: false - ConsoleAPI: true - Point: false - Rect: false - -rules: - # Disabled stuff - no-console: 0 # TODO: Can we use console? - no-cond-assign: 0 - no-fallthrough: 0 diff --git a/mobile/android/chrome/content/ActionBarHandler.js b/mobile/android/chrome/content/ActionBarHandler.js deleted file mode 100644 index 190021043..000000000 --- a/mobile/android/chrome/content/ActionBarHandler.js +++ /dev/null @@ -1,731 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- -/* 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"; - -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); - -const PHONE_REGEX = /^\+?[0-9\s,-.\(\)*#pw]{1,30}$/; // Are we a phone #? - - -/** - * ActionBarHandler Object and methods. Interface between Gecko Text Selection code - * (AccessibleCaret, etc) and the Mobile ActionBar UI. - */ -var ActionBarHandler = { - // Error codes returned from _init(). - START_TOUCH_ERROR: { - NO_CONTENT_WINDOW: "No valid content Window found.", - NONE: "", - }, - - _nextSelectionID: 1, // Next available. - _selectionID: null, // Unique Selection ID, assigned each time we _init(). - - _boundingClientRect: null, // Current selections boundingClientRect. - _actionBarActions: null, // Most-recent set of actions sent to ActionBar. - - /** - * Receive and act on AccessibleCarets caret state-change - * (mozcaretstatechanged) events. - */ - caretStateChangedHandler: function(e) { - // Close an open ActionBar, if carets no longer logically visible. - if (this._selectionID && !e.caretVisible) { - this._uninit(false); - return; - } - - if (!this._selectionID && e.collapsed) { - switch (e.reason) { - case 'longpressonemptycontent': - case 'taponcaret': - // Show ActionBar when long pressing on an empty input or single - // tapping on the caret. - this._init(e.boundingClientRect); - break; - - case 'updateposition': - // Do not show ActionBar when single tapping on an non-empty editable - // input. - break; - - default: - break; - } - return; - } - - // Open a closed ActionBar if carets actually visible. - if (!this._selectionID && e.caretVisuallyVisible) { - this._init(e.boundingClientRect); - return; - } - - // Else, update an open ActionBar. - if (this._selectionID) { - if (!this._selectionHasChanged()) { - // Still the same active selection. - if (e.reason == 'presscaret' || e.reason == 'scroll') { - // boundingClientRect doesn't matter since we are hiding the floating - // toolbar. - this._updateVisibility(); - } else { - // Selection changes update boundingClientRect. - this._boundingClientRect = e.boundingClientRect; - let forceUpdate = e.reason == 'updateposition' || e.reason == 'releasecaret'; - this._sendActionBarActions(forceUpdate); - } - } else { - // We've started a new selection entirely. - this._uninit(false); - this._init(e.boundingClientRect); - } - } - }, - - /** - * ActionBarHandler notification observers. - */ - observe: function(subject, topic, data) { - switch (topic) { - // User click an ActionBar button. - case "TextSelection:Action": { - if (!this._selectionID) { - break; - } - for (let type in this.actions) { - let action = this.actions[type]; - if (action.id == data) { - action.action(this._targetElement, this._contentWindow); - break; - } - } - break; - } - - // Provide selected text to FindInPageBar on request. - case "TextSelection:Get": { - Messaging.sendRequest({ - type: "TextSelection:Data", - requestId: data, - text: this._getSelectedText(), - }); - - this._uninit(); - break; - } - - // User closed ActionBar by clicking "checkmark" button. - case "TextSelection:End": { - // End the requested selection only. - if (this._selectionID == JSON.parse(data).selectionID) { - this._uninit(); - } - break; - } - } - }, - - /** - * Called when Gecko AccessibleCaret becomes visible. - */ - _init: function(boundingClientRect) { - let [element, win] = this._getSelectionTargets(); - if (!win) { - return this.START_TOUCH_ERROR.NO_CONTENT_WINDOW; - } - - // Hold the ActionBar ID provided by Gecko. - this._selectionID = this._nextSelectionID++; - [this._targetElement, this._contentWindow] = [element, win]; - this._boundingClientRect = boundingClientRect; - - // Open the ActionBar, send it's actions list. - Messaging.sendRequest({ - type: "TextSelection:ActionbarInit", - selectionID: this._selectionID, - }); - this._sendActionBarActions(true); - - return this.START_TOUCH_ERROR.NONE; - }, - - /** - * Called when content is scrolled and handles are hidden. - */ - _updateVisibility: function() { - Messaging.sendRequest({ - type: "TextSelection:Visibility", - selectionID: this._selectionID, - }); - }, - - /** - * Determines the window containing the selection, and its - * editable element if present. - */ - _getSelectionTargets: function() { - let [element, win] = [Services.focus.focusedElement, Services.focus.focusedWindow]; - if (!element) { - // No focused editable. - return [null, win]; - } - - // Return focused editable text element and its window. - if (((element instanceof HTMLInputElement) && element.mozIsTextField(false)) || - (element instanceof HTMLTextAreaElement) || - element.isContentEditable) { - return [element, win]; - } - - // Focused element can't contain text. - return [null, win]; - }, - - /** - * The active Selection has changed, if the current focused element / win, - * pair, or state of the win's designMode changes. - */ - _selectionHasChanged: function() { - let [element, win] = this._getSelectionTargets(); - return (this._targetElement !== element || - this._contentWindow !== win || - this._isInDesignMode(this._contentWindow) !== this._isInDesignMode(win)); - }, - - /** - * Called when Gecko AccessibleCaret becomes hidden, - * ActionBar is closed by user "close" request, or as a result of object - * methods such as SELECT_ALL, PASTE, etc. - */ - _uninit: function(clearSelection = true) { - // Bail if there's no active selection. - if (!this._selectionID) { - return; - } - - // Close the ActionBar. - Messaging.sendRequest({ - type: "TextSelection:ActionbarUninit", - }); - - // Clear the selection ID to complete the uninit(), but leave our reference - // to selectionTargets (_targetElement, _contentWindow) in case we need - // a final clearSelection(). - this._selectionID = null; - this._boundingClientRect = null; - - // Clear selection required if triggered by self, or TextSelection icon - // actions. If called by Gecko CaretStateChangedEvent, - // visibility state is already correct. - if (clearSelection) { - this._clearSelection(); - } - }, - - /** - * Final UI cleanup when Actionbar is closed by icon click, or where - * we terminate selection state after before/after actionbar actions - * (Cut, Copy, Paste, Search, Share, Call). - */ - _clearSelection: function(element = this._targetElement, win = this._contentWindow) { - // Commit edit compositions, and clear focus from editables. - if (element) { - let imeSupport = this._getEditor(element, win).QueryInterface(Ci.nsIEditorIMESupport); - if (imeSupport.composing) { - imeSupport.forceCompositionEnd(); - } - element.blur(); - } - - // Remove Selection from non-editables and now-unfocused contentEditables. - if (!element || element.isContentEditable) { - this._getSelection().removeAllRanges(); - } - }, - - /** - * Called to determine current ActionBar actions and send to TextSelection - * handler. By default we only send if current action state differs from - * the previous. - * @param By default we only send an ActionBarStatus update message if - * there is a change from the previous state. sendAlways can be - * set by init() for example, where we want to always send the - * current state. - */ - _sendActionBarActions: function(sendAlways) { - let actions = this._getActionBarActions(); - - let actionCountUnchanged = this._actionBarActions && - actions.length === this._actionBarActions.length; - let actionsMatch = actionCountUnchanged && - this._actionBarActions.every((e,i) => { - return e.id === actions[i].id; - }); - - if (sendAlways || !actionsMatch) { - Messaging.sendRequest({ - type: "TextSelection:ActionbarStatus", - selectionID: this._selectionID, - actions: actions, - x: this._boundingClientRect.x, - y: this._boundingClientRect.y, - width: this._boundingClientRect.width, - height: this._boundingClientRect.height - }); - } - - this._actionBarActions = actions; - }, - - /** - * Determine and return current ActionBar state. - */ - _getActionBarActions: function(element = this._targetElement, win = this._contentWindow) { - let actions = []; - - for (let type in this.actions) { - let action = this.actions[type]; - if (action.selector.matches(element, win)) { - let a = { - id: action.id, - label: this._getActionValue(action, "label", "", element), - icon: this._getActionValue(action, "icon", "drawable://ic_status_logo", element), - order: this._getActionValue(action, "order", 0, element), - floatingOrder: this._getActionValue(action, "floatingOrder", 9, element), - showAsAction: this._getActionValue(action, "showAsAction", true, element), - }; - actions.push(a); - } - } - actions.sort((a, b) => b.order - a.order); - - return actions; - }, - - /** - * Provides a value from an action. If the action defines the value as a function, - * we return the result of calling the function. Otherwise, we return the value - * itself. If the value isn't defined for this action, will return a default. - */ - _getActionValue: function(obj, name, defaultValue, element) { - if (!(name in obj)) - return defaultValue; - - if (typeof obj[name] == "function") - return obj[name](element); - - return obj[name]; - }, - - /** - * Actionbar callback methods. - */ - actions: { - - SELECT_ALL: { - id: "selectall_action", - label: Strings.browser.GetStringFromName("contextmenu.selectAll"), - icon: "drawable://ab_select_all", - order: 5, - floatingOrder: 5, - - selector: { - matches: function(element, win) { - // For editable, check its length. For default contentWindow, assume - // true, else there'd been nothing to long-press to open ActionBar. - return (element) ? element.textLength != 0 : true; - }, - }, - - action: function(element, win) { - // Some Mobile keyboards such as SwiftKeyboard, provide auto-suggest - // style highlights via composition selections in editables. - if (element) { - // If we have an active composition string, commit it, and - // ensure proper element focus. - let imeSupport = ActionBarHandler._getEditor(element, win). - QueryInterface(Ci.nsIEditorIMESupport); - if (imeSupport.composing) { - element.blur(); - element.focus(); - } - } - - // Close ActionBarHandler, then selectAll, and display handles. - ActionBarHandler._getSelectAllController(element, win).selectAll(); - UITelemetry.addEvent("action.1", "actionbar", null, "select_all"); - }, - }, - - CUT: { - id: "cut_action", - label: Strings.browser.GetStringFromName("contextmenu.cut"), - icon: "drawable://ab_cut", - order: 4, - floatingOrder: 1, - - selector: { - matches: function(element, win) { - // Can cut from editable, or design-mode document. - if (!element && !ActionBarHandler._isInDesignMode(win)) { - return false; - } - // Don't allow "cut" from password fields. - if (element instanceof Ci.nsIDOMHTMLInputElement && - !element.mozIsTextField(true)) { - return false; - } - // Don't allow "cut" from disabled/readonly fields. - if (element && (element.disabled || element.readOnly)) { - return false; - } - // Allow if selected text exists. - return (ActionBarHandler._getSelectedText().length > 0); - }, - }, - - action: function(element, win) { - // First copy the selection text to the clipboard. - let selectedText = ActionBarHandler._getSelectedText(); - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper); - clipboard.copyString(selectedText); - - let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied"); - Snackbars.show(msg, Snackbars.LENGTH_LONG); - - // Then cut the selection text. - ActionBarHandler._getSelection(element, win).deleteFromDocument(); - - ActionBarHandler._uninit(); - UITelemetry.addEvent("action.1", "actionbar", null, "cut"); - }, - }, - - COPY: { - id: "copy_action", - label: Strings.browser.GetStringFromName("contextmenu.copy"), - icon: "drawable://ab_copy", - order: 3, - floatingOrder: 2, - - selector: { - matches: function(element, win) { - // Don't allow "copy" from password fields. - if (element instanceof Ci.nsIDOMHTMLInputElement && - !element.mozIsTextField(true)) { - return false; - } - // Allow if selected text exists. - return (ActionBarHandler._getSelectedText().length > 0); - }, - }, - - action: function(element, win) { - let selectedText = ActionBarHandler._getSelectedText(); - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper); - clipboard.copyString(selectedText); - - let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied"); - Snackbars.show(msg, Snackbars.LENGTH_LONG); - - ActionBarHandler._uninit(); - UITelemetry.addEvent("action.1", "actionbar", null, "copy"); - }, - }, - - PASTE: { - id: "paste_action", - label: Strings.browser.GetStringFromName("contextmenu.paste"), - icon: "drawable://ab_paste", - order: 2, - floatingOrder: 3, - - selector: { - matches: function(element, win) { - // Can paste to editable, or design-mode document. - if (!element && !ActionBarHandler._isInDesignMode(win)) { - return false; - } - // Can't paste into disabled/readonly fields. - if (element && (element.disabled || element.readOnly)) { - return false; - } - // Can't paste if Clipboard empty. - let flavors = ["text/unicode"]; - return Services.clipboard.hasDataMatchingFlavors(flavors, flavors.length, - Ci.nsIClipboard.kGlobalClipboard); - }, - }, - - action: function(element, win) { - // Paste the clipboard, then close the ActionBarHandler and ActionBar. - ActionBarHandler._getEditor(element, win). - paste(Ci.nsIClipboard.kGlobalClipboard); - ActionBarHandler._uninit(); - UITelemetry.addEvent("action.1", "actionbar", null, "paste"); - }, - }, - - CALL: { - id: "call_action", - label: Strings.browser.GetStringFromName("contextmenu.call"), - icon: "drawable://phone", - order: 1, - floatingOrder: 0, - - selector: { - matches: function(element, win) { - return (ActionBarHandler._getSelectedPhoneNumber() != null); - }, - }, - - action: function(element, win) { - BrowserApp.loadURI("tel:" + - ActionBarHandler._getSelectedPhoneNumber()); - - ActionBarHandler._uninit(); - UITelemetry.addEvent("action.1", "actionbar", null, "call"); - }, - }, - - SEARCH: { - id: "search_action", - label: () => Strings.browser.formatStringFromName("contextmenu.search", - [Services.search.defaultEngine.name], 1), - icon: "drawable://ab_search", - order: 1, - floatingOrder: 6, - - selector: { - matches: function(element, win) { - // Allow if selected text exists. - return (ActionBarHandler._getSelectedText().length > 0); - }, - }, - - action: function(element, win) { - let selectedText = ActionBarHandler._getSelectedText(); - ActionBarHandler._uninit(); - - // Set current tab as parent of new tab, - // and set new tab as private if the parent is. - let searchSubmission = Services.search.defaultEngine.getSubmission(selectedText); - let parent = BrowserApp.selectedTab; - let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser); - BrowserApp.addTab(searchSubmission.uri.spec, - { parentId: parent.id, - selected: true, - isPrivate: isPrivate, - } - ); - - UITelemetry.addEvent("action.1", "actionbar", null, "search"); - }, - }, - - SEARCH_ADD: { - id: "search_add_action", - label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"), - icon: "drawable://ab_add_search_engine", - order: 0, - floatingOrder: 8, - - selector: { - matches: function(element, win) { - if(!(element instanceof HTMLInputElement)) { - return false; - } - let form = element.form; - if (!form || element.type == "password") { - return false; - } - - let method = form.method.toUpperCase(); - let canAddEngine = (method == "GET") || - (method == "POST" && (form.enctype != "text/plain" && form.enctype != "multipart/form-data")); - if (!canAddEngine) { - return false; - } - - // If SearchEngine query finds it, then we don't want action to add displayed. - if (SearchEngines.visibleEngineExists(element)) { - return false; - } - - return true; - }, - }, - - action: function(element, win) { - UITelemetry.addEvent("action.1", "actionbar", null, "add_search_engine"); - - // Engines are added asynch. If required, update SelectionUI on callback. - SearchEngines.addEngine(element, (result) => { - if (result) { - ActionBarHandler._sendActionBarActions(true); - } - }); - }, - }, - - SHARE: { - id: "share_action", - label: Strings.browser.GetStringFromName("contextmenu.share"), - icon: "drawable://ic_menu_share", - order: 0, - floatingOrder: 4, - - selector: { - matches: function(element, win) { - if (!ParentalControls.isAllowed(ParentalControls.SHARE)) { - return false; - } - // Allow if selected text exists. - return (ActionBarHandler._getSelectedText().length > 0); - }, - }, - - action: function(element, win) { - Messaging.sendRequest({ - type: "Share:Text", - text: ActionBarHandler._getSelectedText(), - }); - - ActionBarHandler._uninit(); - UITelemetry.addEvent("action.1", "actionbar", null, "share"); - }, - }, - }, - - /** - * Provides UUID service for generating action ID's. - */ - get _idService() { - delete this._idService; - return this._idService = Cc["@mozilla.org/uuid-generator;1"]. - getService(Ci.nsIUUIDGenerator); - }, - - /** - * The targetElement holds an editable element containing a - * selection or a caret. - */ - get _targetElement() { - if (this._targetElementRef) - return this._targetElementRef.get(); - return null; - }, - - set _targetElement(element) { - this._targetElementRef = Cu.getWeakReference(element); - }, - - /** - * The contentWindow holds the selection, or the targetElement - * if it's an editable. - */ - get _contentWindow() { - if (this._contentWindowRef) - return this._contentWindowRef.get(); - return null; - }, - - set _contentWindow(aContentWindow) { - this._contentWindowRef = Cu.getWeakReference(aContentWindow); - }, - - /** - * If we have an active selection, is it part of a designMode document? - */ - _isInDesignMode: function(win) { - return this._selectionID && (win.document.designMode === "on"); - }, - - /** - * Provides the currently selected text, for either an editable, - * or for the default contentWindow. - */ - _getSelectedText: function() { - // Can be called from FindInPageBar "TextSelection:Get", when there - // is no active selection. - if (!this._selectionID) { - return ""; - } - - let selection = this._getSelection(); - - // Textarea can contain LF, etc. - if (this._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement) { - let flags = Ci.nsIDocumentEncoder.OutputPreformatted | - Ci.nsIDocumentEncoder.OutputRaw; - return selection.QueryInterface(Ci.nsISelectionPrivate). - toStringWithFormat("text/plain", flags, 0); - } - - // Return explicitly selected text. - return selection.toString(); - }, - - /** - * Provides the nsISelection for either an editor, or from the - * default window. - */ - _getSelection: function(element = this._targetElement, win = this._contentWindow) { - return (element instanceof Ci.nsIDOMNSEditableElement) ? - this._getEditor(element).selection : - win.getSelection(); - }, - - /** - * Returns an nsEditor or nsHTMLEditor. - */ - _getEditor: function(element = this._targetElement, win = this._contentWindow) { - if (element instanceof Ci.nsIDOMNSEditableElement) { - return element.QueryInterface(Ci.nsIDOMNSEditableElement).editor; - } - - return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation). - QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession). - getEditorForWindow(win); - }, - - /** - * Returns a selection controller. - */ - _getSelectionController: function(element = this._targetElement, win = this._contentWindow) { - if (element instanceof Ci.nsIDOMNSEditableElement) { - return this._getEditor(element, win).selectionController; - } - - return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation). - QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay). - QueryInterface(Ci.nsISelectionController); - }, - - /** - * For selectAll(), provides the editor, or the default window selection Controller. - */ - _getSelectAllController: function(element = this._targetElement, win = this._contentWindow) { - let editor = this._getEditor(element, win); - return (editor) ? - editor : this._getSelectionController(element, win); - }, - - /** - * Call / Phone Helper methods. - */ - _getSelectedPhoneNumber: function() { - let selectedText = this._getSelectedText().trim(); - return this._isPhoneNumber(selectedText) ? - selectedText : null; - }, - - _isPhoneNumber: function(selectedText) { - return (PHONE_REGEX.test(selectedText)); - }, -}; diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js deleted file mode 100644 index 76773c4d8..000000000 --- a/mobile/android/chrome/content/CastingApps.js +++ /dev/null @@ -1,850 +0,0 @@ -// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- -/* 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"; - -XPCOMUtils.defineLazyModuleGetter(this, "PageActions", - "resource://gre/modules/PageActions.jsm"); - -// Define service devices. We should consider moving these to their respective -// JSM files, but we left them here to allow for better lazy JSM loading. -var rokuDevice = { - id: "roku:ecp", - target: "roku:ecp", - factory: function(aService) { - Cu.import("resource://gre/modules/RokuApp.jsm"); - return new RokuApp(aService); - }, - types: ["video/mp4"], - extensions: ["mp4"] -}; - -var mediaPlayerDevice = { - id: "media:router", - target: "media:router", - factory: function(aService) { - Cu.import("resource://gre/modules/MediaPlayerApp.jsm"); - return new MediaPlayerApp(aService); - }, - types: ["video/mp4", "video/webm", "application/x-mpegurl"], - extensions: ["mp4", "webm", "m3u", "m3u8"], - init: function() { - Services.obs.addObserver(this, "MediaPlayer:Added", false); - Services.obs.addObserver(this, "MediaPlayer:Changed", false); - Services.obs.addObserver(this, "MediaPlayer:Removed", false); - }, - observe: function(subject, topic, data) { - if (topic === "MediaPlayer:Added") { - let service = this.toService(JSON.parse(data)); - SimpleServiceDiscovery.addService(service); - } else if (topic === "MediaPlayer:Changed") { - let service = this.toService(JSON.parse(data)); - SimpleServiceDiscovery.updateService(service); - } else if (topic === "MediaPlayer:Removed") { - SimpleServiceDiscovery.removeService(data); - } - }, - toService: function(display) { - // Convert the native data into something matching what is created in _processService() - return { - location: display.location, - target: "media:router", - friendlyName: display.friendlyName, - uuid: display.uuid, - manufacturer: display.manufacturer, - modelName: display.modelName, - mirror: display.mirror - }; - } -}; - -var fxOSTVDevice = { - id: "app://fling-player.gaiamobile.org", - target: "app://fling-player.gaiamobile.org/index.html", - factory: function(aService) { - Cu.import("resource://gre/modules/PresentationApp.jsm"); - let request = new window.PresentationRequest(this.target); - return new PresentationApp(aService, request); - }, - init: function() { - Services.obs.addObserver(this, "presentation-device-change", false); - SimpleServiceDiscovery.addExternalDiscovery(this); - }, - observe: function(subject, topic, data) { - let device = subject.QueryInterface(Ci.nsIPresentationDevice); - let service = this.toService(device); - switch (data) { - case "add": - SimpleServiceDiscovery.addService(service); - break; - case "update": - SimpleServiceDiscovery.updateService(service); - break; - case "remove": - if(SimpleServiceDiscovery.findServiceForID(device.id)) { - SimpleServiceDiscovery.removeService(device.id); - } - break; - } - }, - toService: function(device) { - return { - location: device.id, - target: fxOSTVDevice.target, - friendlyName: device.name, - uuid: device.id, - manufacturer: "Firefox OS TV", - modelName: "Firefox OS TV", - }; - }, - startDiscovery: function() { - window.navigator.mozPresentationDeviceInfo.forceDiscovery(); - - // need to update the lastPing time for known device. - window.navigator.mozPresentationDeviceInfo.getAll() - .then(function(devices) { - for (let device of devices) { - let service = fxOSTVDevice.toService(device); - SimpleServiceDiscovery.addService(service); - } - }); - }, - stopDiscovery: function() { - // do nothing - }, - types: ["video/mp4", "video/webm"], - extensions: ["mp4", "webm"], -}; - -var CastingApps = { - _castMenuId: -1, - mirrorStartMenuId: -1, - mirrorStopMenuId: -1, - _blocked: null, - _bound: null, - _interval: 120 * 1000, // 120 seconds - - init: function ca_init() { - if (!this.isCastingEnabled()) { - return; - } - - // Register targets - SimpleServiceDiscovery.registerDevice(rokuDevice); - - // MediaPlayerDevice will notify us any time the native device list changes. - mediaPlayerDevice.init(); - SimpleServiceDiscovery.registerDevice(mediaPlayerDevice); - - // Presentation Device will notify us any time the available device list changes. - if (window.PresentationRequest) { - fxOSTVDevice.init(); - SimpleServiceDiscovery.registerDevice(fxOSTVDevice); - } - - // Search for devices continuously - SimpleServiceDiscovery.search(this._interval); - - this._castMenuId = NativeWindow.contextmenus.add( - Strings.browser.GetStringFromName("contextmenu.sendToDevice"), - this.filterCast, - this.handleContextMenu.bind(this) - ); - - Services.obs.addObserver(this, "Casting:Play", false); - Services.obs.addObserver(this, "Casting:Pause", false); - Services.obs.addObserver(this, "Casting:Stop", false); - Services.obs.addObserver(this, "Casting:Mirror", false); - Services.obs.addObserver(this, "ssdp-service-found", false); - Services.obs.addObserver(this, "ssdp-service-lost", false); - Services.obs.addObserver(this, "application-background", false); - Services.obs.addObserver(this, "application-foreground", false); - - BrowserApp.deck.addEventListener("TabSelect", this, true); - BrowserApp.deck.addEventListener("pageshow", this, true); - BrowserApp.deck.addEventListener("playing", this, true); - BrowserApp.deck.addEventListener("ended", this, true); - BrowserApp.deck.addEventListener("MozAutoplayMediaBlocked", this, true); - // Note that the XBL binding is untrusted - BrowserApp.deck.addEventListener("MozNoControlsVideoBindingAttached", this, true, true); - }, - - _mirrorStarted: function(stopMirrorCallback) { - this.stopMirrorCallback = stopMirrorCallback; - NativeWindow.menu.update(this.mirrorStartMenuId, { visible: false }); - NativeWindow.menu.update(this.mirrorStopMenuId, { visible: true }); - }, - - serviceAdded: function(aService) { - if (this.isMirroringEnabled() && aService.mirror && this.mirrorStartMenuId == -1) { - this.mirrorStartMenuId = NativeWindow.menu.add({ - name: Strings.browser.GetStringFromName("casting.mirrorTab"), - callback: function() { - let callbackFunc = function(aService) { - let app = SimpleServiceDiscovery.findAppForService(aService); - if (app) { - app.mirror(function() {}, window, BrowserApp.selectedTab.getViewport(), this._mirrorStarted.bind(this), window.BrowserApp.selectedBrowser.contentWindow); - } - }.bind(this); - - this.prompt(callbackFunc, aService => aService.mirror); - }.bind(this), - parent: NativeWindow.menu.toolsMenuID - }); - - this.mirrorStopMenuId = NativeWindow.menu.add({ - name: Strings.browser.GetStringFromName("casting.mirrorTabStop"), - callback: function() { - if (this.tabMirror) { - this.tabMirror.stop(); - this.tabMirror = null; - } else if (this.stopMirrorCallback) { - this.stopMirrorCallback(); - this.stopMirrorCallback = null; - } - NativeWindow.menu.update(this.mirrorStartMenuId, { visible: true }); - NativeWindow.menu.update(this.mirrorStopMenuId, { visible: false }); - }.bind(this), - }); - } - if (this.mirrorStartMenuId != -1) { - NativeWindow.menu.update(this.mirrorStopMenuId, { visible: false }); - } - }, - - serviceLost: function(aService) { - if (aService.mirror && this.mirrorStartMenuId != -1) { - let haveMirror = false; - SimpleServiceDiscovery.services.forEach(function(service) { - if (service.mirror) { - haveMirror = true; - } - }); - if (!haveMirror) { - NativeWindow.menu.remove(this.mirrorStartMenuId); - this.mirrorStartMenuId = -1; - } - } - }, - - isCastingEnabled: function isCastingEnabled() { - return Services.prefs.getBoolPref("browser.casting.enabled"); - }, - - isMirroringEnabled: function isMirroringEnabled() { - return Services.prefs.getBoolPref("browser.mirroring.enabled"); - }, - - observe: function (aSubject, aTopic, aData) { - switch (aTopic) { - case "Casting:Play": - if (this.session && this.session.remoteMedia.status == "paused") { - this.session.remoteMedia.play(); - } - break; - case "Casting:Pause": - if (this.session && this.session.remoteMedia.status == "started") { - this.session.remoteMedia.pause(); - } - break; - case "Casting:Stop": - if (this.session) { - this.closeExternal(); - } - break; - case "Casting:Mirror": - { - Cu.import("resource://gre/modules/TabMirror.jsm"); - this.tabMirror = new TabMirror(aData, window); - NativeWindow.menu.update(this.mirrorStartMenuId, { visible: false }); - NativeWindow.menu.update(this.mirrorStopMenuId, { visible: true }); - } - break; - case "ssdp-service-found": - this.serviceAdded(SimpleServiceDiscovery.findServiceForID(aData)); - break; - case "ssdp-service-lost": - this.serviceLost(SimpleServiceDiscovery.findServiceForID(aData)); - break; - case "application-background": - // Turn off polling while in the background - this._interval = SimpleServiceDiscovery.search(0); - SimpleServiceDiscovery.stopSearch(); - break; - case "application-foreground": - // Turn polling on when app comes back to foreground - SimpleServiceDiscovery.search(this._interval); - break; - } - }, - - handleEvent: function(aEvent) { - switch (aEvent.type) { - case "TabSelect": { - let tab = BrowserApp.getTabForBrowser(aEvent.target); - this._updatePageActionForTab(tab, aEvent); - break; - } - case "pageshow": { - let tab = BrowserApp.getTabForWindow(aEvent.originalTarget.defaultView); - this._updatePageActionForTab(tab, aEvent); - break; - } - case "playing": - case "ended": { - let video = aEvent.target; - if (video instanceof HTMLVideoElement) { - // If playing, send the <video>, but if ended we send nothing to shutdown the pageaction - this._updatePageActionForVideo(aEvent.type === "playing" ? video : null); - } - break; - } - case "MozAutoplayMediaBlocked": { - if (this._bound && this._bound.has(aEvent.target)) { - aEvent.target.dispatchEvent(new CustomEvent("MozNoControlsBlockedVideo")); - } else { - if (!this._blocked) { - this._blocked = new WeakMap; - } - this._blocked.set(aEvent.target, true); - } - break; - } - case "MozNoControlsVideoBindingAttached": { - if (!this._bound) { - this._bound = new WeakMap; - } - this._bound.set(aEvent.target, true); - if (this._blocked && this._blocked.has(aEvent.target)) { - this._blocked.delete(aEvent.target); - aEvent.target.dispatchEvent(new CustomEvent("MozNoControlsBlockedVideo")); - } - break; - } - } - }, - - _sendEventToVideo: function _sendEventToVideo(aElement, aData) { - let event = aElement.ownerDocument.createEvent("CustomEvent"); - event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(aData)); - aElement.dispatchEvent(event); - }, - - handleVideoBindingAttached: function handleVideoBindingAttached(aTab, aEvent) { - // Let's figure out if we have everything needed to cast a video. The binding - // defaults to |false| so we only need to send an event if |true|. - let video = aEvent.target; - if (!(video instanceof HTMLVideoElement)) { - return; - } - - if (SimpleServiceDiscovery.services.length == 0) { - return; - } - - this.getVideo(video, 0, 0, (aBundle) => { - // Let the binding know casting is allowed - if (aBundle) { - this._sendEventToVideo(aBundle.element, { allow: true }); - } - }); - }, - - handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) { - // The binding wants to start a casting session - let video = aEvent.target; - if (!(video instanceof HTMLVideoElement)) { - return; - } - - // Close an existing session first. closeExternal has checks for an exsting - // session and handles remote and video binding shutdown. - this.closeExternal(); - - // Start the new session - UITelemetry.addEvent("cast.1", "button", null); - this.openExternal(video, 0, 0); - }, - - makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) { - return Services.io.newURI(aURL, aOriginCharset, aBaseURI); - }, - - allowableExtension: function(aURI, aExtensions) { - return (aURI instanceof Ci.nsIURL) && aExtensions.indexOf(aURI.fileExtension) != -1; - }, - - allowableMimeType: function(aType, aTypes) { - return aTypes.indexOf(aType) != -1; - }, - - // This method will look at the aElement (or try to find a video at aX, aY) that has - // a castable source. If found, aCallback will be called with a JSON meta bundle. If - // no castable source was found, aCallback is called with null. - getVideo: function(aElement, aX, aY, aCallback) { - let extensions = SimpleServiceDiscovery.getSupportedExtensions(); - let types = SimpleServiceDiscovery.getSupportedMimeTypes(); - - // Fast path: Is the given element a video element? - if (aElement instanceof HTMLVideoElement) { - // If we found a video element, no need to look further, even if no - // castable video source is found. - this._getVideo(aElement, types, extensions, aCallback); - return; - } - - // Maybe this is an overlay, with the video element under it. - // Use the (x, y) location to guess at a <video> element. - - // The context menu system will keep walking up the DOM giving us a chance - // to find an element we match. When it hits <html> things can go BOOM. - try { - let elements = aElement.ownerDocument.querySelectorAll("video"); - for (let element of elements) { - // Look for a video element contained in the overlay bounds - let rect = element.getBoundingClientRect(); - if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) { - // Once we find a <video> under the overlay, we check it and exit. - this._getVideo(element, types, extensions, aCallback); - return; - } - } - } catch(e) {} - }, - - _getContentTypeForURI: function(aURI, aElement, aCallback) { - let channel; - try { - let secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; - if (aElement.crossOrigin) { - secFlags = Ci.nsILoadInfo.SEC_REQUIRE_CORS_DATA_INHERITS; - if (aElement.crossOrigin === "use-credentials") { - secFlags |= Ci.nsILoadInfo.SEC_COOKIES_INCLUDE; - } - } - channel = NetUtil.newChannel({ - uri: aURI, - loadingNode: aElement, - securityFlags: secFlags, - contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_VIDEO - }); - } catch(e) { - aCallback(null); - return; - } - - let listener = { - onStartRequest: function(request, context) { - switch (channel.responseStatus) { - case 301: - case 302: - case 303: - request.cancel(0); - let location = channel.getResponseHeader("Location"); - CastingApps._getContentTypeForURI(CastingApps.makeURI(location), aElement, aCallback); - break; - default: - aCallback(channel.contentType); - request.cancel(0); - break; - } - }, - onStopRequest: function(request, context, statusCode) {}, - onDataAvailable: function(request, context, stream, offset, count) {} - }; - - if (channel) { - channel.asyncOpen2(listener); - } else { - aCallback(null); - } - }, - - // Because this method uses a callback, make sure we return ASAP if we know - // we have a castable video source. - _getVideo: function(aElement, aTypes, aExtensions, aCallback) { - // Keep a list of URIs we need for an async mimetype check - let asyncURIs = []; - - // Grab the poster attribute from the <video> - let posterURL = aElement.poster; - - // First, look to see if the <video> has a src attribute - let sourceURL = aElement.src; - - // If empty, try the currentSrc - if (!sourceURL) { - sourceURL = aElement.currentSrc; - } - - if (sourceURL) { - // Use the file extension to guess the mime type - let sourceURI = this.makeURI(sourceURL, null, this.makeURI(aElement.baseURI)); - if (this.allowableExtension(sourceURI, aExtensions)) { - aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI}); - return; - } - - if (aElement.type) { - // Fast sync check - if (this.allowableMimeType(aElement.type, aTypes)) { - aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aElement.type }); - return; - } - } - - // Delay the async check until we sync scan all possible URIs - asyncURIs.push(sourceURI); - } - - // Next, look to see if there is a <source> child element that meets - // our needs - let sourceNodes = aElement.getElementsByTagName("source"); - for (let sourceNode of sourceNodes) { - let sourceURI = this.makeURI(sourceNode.src, null, this.makeURI(sourceNode.baseURI)); - - // Using the type attribute is our ideal way to guess the mime type. Otherwise, - // fallback to using the file extension to guess the mime type - if (this.allowableExtension(sourceURI, aExtensions)) { - aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type }); - return; - } - - if (sourceNode.type) { - // Fast sync check - if (this.allowableMimeType(sourceNode.type, aTypes)) { - aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type }); - return; - } - } - - // Delay the async check until we sync scan all possible URIs - asyncURIs.push(sourceURI); - } - - // Helper method that walks the array of possible URIs, fetching the mimetype as we go. - // As soon as we find a good sourceURL, avoid firing the callback any further - var _getContentTypeForURIs = (aURIs) => { - // Do an async fetch to figure out the mimetype of the source video - let sourceURI = aURIs.pop(); - this._getContentTypeForURI(sourceURI, aElement, (aType) => { - if (this.allowableMimeType(aType, aTypes)) { - // We found a supported mimetype. - aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aType }); - } else { - // This URI was not a supported mimetype, so let's try the next, if we have more. - if (aURIs.length > 0) { - _getContentTypeForURIs(aURIs); - } else { - // We were not able to find a supported mimetype. - aCallback(null); - } - } - }); - } - - // If we didn't find a good URI directly, let's look using async methods. - if (asyncURIs.length > 0) { - _getContentTypeForURIs(asyncURIs); - } - }, - - // This code depends on handleVideoBindingAttached setting mozAllowCasting - // so we can quickly figure out if the video is castable - isVideoCastable: function(aElement, aX, aY) { - // Use the flag set when the <video> binding was created as the check - if (aElement instanceof HTMLVideoElement) { - return aElement.mozAllowCasting; - } - - // This is called by the context menu system and the system will keep - // walking up the DOM giving us a chance to find an element we match. - // When it hits <html> things can go BOOM. - try { - // Maybe this is an overlay, with the video element under it - // Use the (x, y) location to guess at a <video> element - let elements = aElement.ownerDocument.querySelectorAll("video"); - for (let element of elements) { - // Look for a video element contained in the overlay bounds - let rect = element.getBoundingClientRect(); - if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) { - // Use the flag set when the <video> binding was created as the check - return element.mozAllowCasting; - } - } - } catch(e) {} - - return false; - }, - - filterCast: { - matches: function(aElement, aX, aY) { - // This behavior matches the pageaction: As long as a video is castable, - // we can cast it, even if it's already being cast to a device. - if (SimpleServiceDiscovery.services.length == 0) - return false; - return CastingApps.isVideoCastable(aElement, aX, aY); - } - }, - - pageAction: { - click: function() { - // Since this is a pageaction, we use the selected browser - let browser = BrowserApp.selectedBrowser; - if (!browser) { - return; - } - - // Look for a castable <video> that is playing, and start casting it - let videos = browser.contentDocument.querySelectorAll("video"); - for (let video of videos) { - if (!video.paused && video.mozAllowCasting) { - UITelemetry.addEvent("cast.1", "pageaction", null); - CastingApps.openExternal(video, 0, 0); - return; - } - } - } - }, - - _findCastableVideo: function _findCastableVideo(aBrowser) { - if (!aBrowser) { - return null; - } - - // Scan for a <video> being actively cast. Also look for a castable <video> - // on the page. - let castableVideo = null; - let videos = aBrowser.contentDocument.querySelectorAll("video"); - for (let video of videos) { - if (video.mozIsCasting) { - // This <video> is cast-active. Break out of loop. - return video; - } - - if (!video.paused && video.mozAllowCasting) { - // This <video> is cast-ready. Keep looking so cast-active could be found. - castableVideo = video; - } - } - - // Could be null - return castableVideo; - }, - - _updatePageActionForTab: function _updatePageActionForTab(aTab, aEvent) { - // We only care about events on the selected tab - if (aTab != BrowserApp.selectedTab) { - return; - } - - // Update the page action, scanning for a castable <video> - this._updatePageAction(); - }, - - _updatePageActionForVideo: function _updatePageActionForVideo(aVideo) { - this._updatePageAction(aVideo); - }, - - _updatePageAction: function _updatePageAction(aVideo) { - // Remove any exising pageaction first, in case state changes or we don't have - // a castable video - if (this.pageAction.id) { - PageActions.remove(this.pageAction.id); - delete this.pageAction.id; - } - - if (!aVideo) { - aVideo = this._findCastableVideo(BrowserApp.selectedBrowser); - if (!aVideo) { - return; - } - } - - // We only show pageactions if the <video> is from the selected tab - if (BrowserApp.selectedTab != BrowserApp.getTabForWindow(aVideo.ownerDocument.defaultView.top)) { - return; - } - - // We check for two state here: - // 1. The video is actively being cast - // 2. The video is allowed to be cast and is currently playing - // Both states have the same action: Show the cast page action - if (aVideo.mozIsCasting) { - this.pageAction.id = PageActions.add({ - title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"), - icon: "drawable://casting_active", - clickCallback: this.pageAction.click, - important: true - }); - } else if (aVideo.mozAllowCasting) { - this.pageAction.id = PageActions.add({ - title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"), - icon: "drawable://casting", - clickCallback: this.pageAction.click, - important: true - }); - } - }, - - prompt: function(aCallback, aFilterFunc) { - let items = []; - let filteredServices = []; - SimpleServiceDiscovery.services.forEach(function(aService) { - let item = { - label: aService.friendlyName, - selected: false - }; - if (!aFilterFunc || aFilterFunc(aService)) { - filteredServices.push(aService); - items.push(item); - } - }); - - if (items.length == 0) { - return; - } - - let prompt = new Prompt({ - title: Strings.browser.GetStringFromName("casting.sendToDevice") - }).setSingleChoiceItems(items).show(function(data) { - let selected = data.button; - let service = selected == -1 ? null : filteredServices[selected]; - if (aCallback) - aCallback(service); - }); - }, - - handleContextMenu: function(aElement, aX, aY) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_cast"); - UITelemetry.addEvent("cast.1", "contextmenu", null); - this.openExternal(aElement, aX, aY); - }, - - openExternal: function(aElement, aX, aY) { - // Start a second screen media service - this.getVideo(aElement, aX, aY, this._openExternal.bind(this)); - }, - - _openExternal: function(aVideo) { - if (!aVideo) { - return; - } - - function filterFunc(aService) { - return this.allowableExtension(aVideo.sourceURI, aService.extensions) || this.allowableMimeType(aVideo.type, aService.types); - } - - this.prompt(function(aService) { - if (!aService) - return; - - // Make sure we have a player app for the given service - let app = SimpleServiceDiscovery.findAppForService(aService); - if (!app) - return; - - if (aVideo.element) { - aVideo.title = aVideo.element.ownerDocument.defaultView.top.document.title; - - // If the video is currently playing on the device, pause it - if (!aVideo.element.paused) { - aVideo.element.pause(); - } - } - - app.stop(function() { - app.start(function(aStarted) { - if (!aStarted) { - dump("CastingApps: Unable to start app"); - return; - } - - app.remoteMedia(function(aRemoteMedia) { - if (!aRemoteMedia) { - dump("CastingApps: Failed to create remotemedia"); - return; - } - - this.session = { - service: aService, - app: app, - remoteMedia: aRemoteMedia, - data: { - title: aVideo.title, - source: aVideo.source, - poster: aVideo.poster - }, - videoRef: Cu.getWeakReference(aVideo.element) - }; - }.bind(this), this); - }.bind(this)); - }.bind(this)); - }.bind(this), filterFunc.bind(this)); - }, - - closeExternal: function() { - if (!this.session) { - return; - } - - this.session.remoteMedia.shutdown(); - this._shutdown(); - }, - - _shutdown: function() { - if (!this.session) { - return; - } - - this.session.app.stop(); - let video = this.session.videoRef.get(); - if (video) { - this._sendEventToVideo(video, { active: false }); - this._updatePageAction(); - } - - delete this.session; - }, - - // RemoteMedia callback API methods - onRemoteMediaStart: function(aRemoteMedia) { - if (!this.session) { - return; - } - - aRemoteMedia.load(this.session.data); - Messaging.sendRequest({ type: "Casting:Started", device: this.session.service.friendlyName }); - - let video = this.session.videoRef.get(); - if (video) { - this._sendEventToVideo(video, { active: true }); - this._updatePageAction(video); - } - }, - - onRemoteMediaStop: function(aRemoteMedia) { - Messaging.sendRequest({ type: "Casting:Stopped" }); - this._shutdown(); - }, - - onRemoteMediaStatus: function(aRemoteMedia) { - if (!this.session) { - return; - } - - let status = aRemoteMedia.status; - switch (status) { - case "started": - Messaging.sendRequest({ type: "Casting:Playing" }); - break; - case "paused": - Messaging.sendRequest({ type: "Casting:Paused" }); - break; - case "completed": - this.closeExternal(); - break; - } - } -}; diff --git a/mobile/android/chrome/content/ConsoleAPI.js b/mobile/android/chrome/content/ConsoleAPI.js deleted file mode 100644 index 6ba4c1195..000000000 --- a/mobile/android/chrome/content/ConsoleAPI.js +++ /dev/null @@ -1,96 +0,0 @@ -/* 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"; - -var ConsoleAPI = { - observe: function observe(aMessage, aTopic, aData) { - aMessage = aMessage.wrappedJSObject; - - let mappedArguments = Array.map(aMessage.arguments, this.formatResult, this); - let joinedArguments = Array.join(mappedArguments, " "); - - if (aMessage.level == "error" || aMessage.level == "warn") { - let flag = (aMessage.level == "error" ? Ci.nsIScriptError.errorFlag : Ci.nsIScriptError.warningFlag); - let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError); - consoleMsg.init(joinedArguments, null, null, 0, 0, flag, "content javascript"); - Services.console.logMessage(consoleMsg); - } else if (aMessage.level == "trace") { - let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); - let args = aMessage.arguments; - let filename = this.abbreviateSourceURL(args[0].filename); - let functionName = args[0].functionName || bundle.GetStringFromName("stacktrace.anonymousFunction"); - let lineNumber = args[0].lineNumber; - - let body = bundle.formatStringFromName("stacktrace.outputMessage", [filename, functionName, lineNumber], 3); - body += "\n"; - args.forEach(function(aFrame) { - let functionName = aFrame.functionName || bundle.GetStringFromName("stacktrace.anonymousFunction"); - body += " " + aFrame.filename + " :: " + functionName + " :: " + aFrame.lineNumber + "\n"; - }); - - Services.console.logStringMessage(body); - } else if (aMessage.level == "time" && aMessage.arguments) { - let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); - let body = bundle.formatStringFromName("timer.start", [aMessage.arguments.name], 1); - Services.console.logStringMessage(body); - } else if (aMessage.level == "timeEnd" && aMessage.arguments) { - let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); - let body = bundle.formatStringFromName("timer.end", [aMessage.arguments.name, aMessage.arguments.duration], 2); - Services.console.logStringMessage(body); - } else if (["group", "groupCollapsed", "groupEnd"].indexOf(aMessage.level) != -1) { - // Do nothing yet - } else { - Services.console.logStringMessage(joinedArguments); - } - }, - - getResultType: function getResultType(aResult) { - let type = aResult === null ? "null" : typeof aResult; - if (type == "object" && aResult.constructor && aResult.constructor.name) - type = aResult.constructor.name; - return type.toLowerCase(); - }, - - formatResult: function formatResult(aResult) { - let output = ""; - let type = this.getResultType(aResult); - switch (type) { - case "string": - case "boolean": - case "date": - case "error": - case "number": - case "regexp": - output = aResult.toString(); - break; - case "null": - case "undefined": - output = type; - break; - default: - output = aResult.toString(); - break; - } - - return output; - }, - - abbreviateSourceURL: function abbreviateSourceURL(aSourceURL) { - // Remove any query parameters. - let hookIndex = aSourceURL.indexOf("?"); - if (hookIndex > -1) - aSourceURL = aSourceURL.substring(0, hookIndex); - - // Remove a trailing "/". - if (aSourceURL[aSourceURL.length - 1] == "/") - aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1); - - // Remove all but the last path component. - let slashIndex = aSourceURL.lastIndexOf("/"); - if (slashIndex > -1) - aSourceURL = aSourceURL.substring(slashIndex + 1); - - return aSourceURL; - } -}; diff --git a/mobile/android/chrome/content/EmbedRT.js b/mobile/android/chrome/content/EmbedRT.js deleted file mode 100644 index 8e35a3b63..000000000 --- a/mobile/android/chrome/content/EmbedRT.js +++ /dev/null @@ -1,82 +0,0 @@ -/* 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"; - -XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI", - "resource://gre/modules/Console.jsm"); - -/* - * Collection of methods and features specific to using a GeckoView instance. - * The code is isolated from browser.js for code size and performance reasons. - */ -var EmbedRT = { - _scopes: {}, - - observe: function(subject, topic, data) { - switch(topic) { - case "GeckoView:ImportScript": - this.importScript(data); - break; - } - }, - - /* - * Loads a script file into a sandbox and calls an optional load function - */ - importScript: function(scriptURL) { - if (scriptURL in this._scopes) { - return; - } - - let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); - - let sandbox = new Cu.Sandbox(principal, - { - sandboxName: scriptURL, - wantGlobalProperties: ["indexedDB"] - } - ); - - sandbox["console"] = new ConsoleAPI({ consoleID: "script/" + scriptURL }); - sandbox["GeckoView"] = { - sendRequest: function(data) { - if (!data) { - throw new Error("Invalid parameter: 'data' can't be null."); - } - - let message = { type: "GeckoView:Message", data: data }; - Messaging.sendRequest(message); - }, - sendRequestForResult: function(data) { - if (!data) { - throw new Error("Invalid parameter: 'data' can't be null."); - } - - let message = { type: "GeckoView:Message", data: data }; - return Messaging.sendRequestForResult(message); - } - }; - - // As we don't want our caller to control the JS version used for the - // script file, we run loadSubScript within the context of the - // sandbox with the latest JS version set explicitly. - sandbox.__SCRIPT_URI_SPEC__ = scriptURL; - Cu.evalInSandbox("Components.classes['@mozilla.org/moz/jssubscript-loader;1'].createInstance(Components.interfaces.mozIJSSubScriptLoader).loadSubScript(__SCRIPT_URI_SPEC__);", sandbox, "ECMAv5"); - - this._scopes[scriptURL] = sandbox; - - if ("load" in sandbox) { - let params = { - window: window, - resourceURI: scriptURL, - }; - - try { - sandbox["load"](params); - } catch(e) { - dump("Exception calling 'load' method in script: " + scriptURL + "\n" + e); - } - } - } -}; diff --git a/mobile/android/chrome/content/FeedHandler.js b/mobile/android/chrome/content/FeedHandler.js deleted file mode 100644 index 91d73ee8d..000000000 --- a/mobile/android/chrome/content/FeedHandler.js +++ /dev/null @@ -1,120 +0,0 @@ -/* 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"; - -var FeedHandler = { - PREF_CONTENTHANDLERS_BRANCH: "browser.contentHandlers.types.", - TYPE_MAYBE_FEED: "application/vnd.mozilla.maybe.feed", - - _contentTypes: null, - - getContentHandlers: function fh_getContentHandlers(contentType) { - if (!this._contentTypes) - this.loadContentHandlers(); - - if (!(contentType in this._contentTypes)) - return []; - - return this._contentTypes[contentType]; - }, - - loadContentHandlers: function fh_loadContentHandlers() { - this._contentTypes = {}; - - let kids = Services.prefs.getBranch(this.PREF_CONTENTHANDLERS_BRANCH).getChildList(""); - - // First get the numbers of the providers by getting all ###.uri prefs - let nums = []; - for (let i = 0; i < kids.length; i++) { - let match = /^(\d+)\.uri$/.exec(kids[i]); - if (!match) - continue; - else - nums.push(match[1]); - } - - // Sort them, to get them back in order - nums.sort(function(a, b) { return a - b; }); - - // Now register them - for (let i = 0; i < nums.length; i++) { - let branch = Services.prefs.getBranch(this.PREF_CONTENTHANDLERS_BRANCH + nums[i] + "."); - let vals = branch.getChildList(""); - if (vals.length == 0) - return; - - try { - let type = branch.getCharPref("type"); - let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data; - let title = branch.getComplexValue("title", Ci.nsIPrefLocalizedString).data; - - if (!(type in this._contentTypes)) - this._contentTypes[type] = []; - this._contentTypes[type].push({ contentType: type, uri: uri, name: title }); - } - catch(ex) {} - } - }, - - observe: function fh_observe(aSubject, aTopic, aData) { - if (aTopic === "Feeds:Subscribe") { - let args = JSON.parse(aData); - let tab = BrowserApp.getTabForId(args.tabId); - if (!tab) - return; - - let browser = tab.browser; - let feeds = browser.feeds; - if (feeds == null) - return; - - // First, let's decide on which feed to subscribe - let feedIndex = -1; - if (feeds.length > 1) { - let p = new Prompt({ - window: browser.contentWindow, - title: Strings.browser.GetStringFromName("feedHandler.chooseFeed") - }).setSingleChoiceItems(feeds.map(function(feed) { - return { label: feed.title || feed.href } - })).show((function(data) { - feedIndex = data.button; - if (feedIndex == -1) - return; - - this.loadFeed(feeds[feedIndex], browser); - }).bind(this)); - return; - } - - this.loadFeed(feeds[0], browser); - } - }, - - loadFeed: function fh_loadFeed(aFeed, aBrowser) { - let feedURL = aFeed.href; - - // Next, we decide on which service to send the feed - let handlers = this.getContentHandlers(this.TYPE_MAYBE_FEED); - if (handlers.length == 0) - return; - - // JSON for Prompt - let p = new Prompt({ - window: aBrowser.contentWindow, - title: Strings.browser.GetStringFromName("feedHandler.subscribeWith") - }).setSingleChoiceItems(handlers.map(function(handler) { - return { label: handler.name }; - })).show(function(data) { - if (data.button == -1) - return; - - // Merge the handler URL and the feed URL - let readerURL = handlers[data.button].uri; - readerURL = readerURL.replace(/%s/gi, encodeURIComponent(feedURL)); - - // Open the resultant URL in a new tab - BrowserApp.addTab(readerURL, { parentId: BrowserApp.selectedTab.id }); - }); - } -}; diff --git a/mobile/android/chrome/content/Feedback.js b/mobile/android/chrome/content/Feedback.js deleted file mode 100644 index 8727c46c3..000000000 --- a/mobile/android/chrome/content/Feedback.js +++ /dev/null @@ -1,64 +0,0 @@ -/* 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"; - -var Feedback = { - - get _feedbackURL() { - delete this._feedbackURL; - return this._feedbackURL = Services.urlFormatter.formatURLPref("app.feedbackURL"); - }, - - observe: function(aMessage, aTopic, aData) { - if (aTopic !== "Feedback:Show") { - return; - } - - // Don't prompt for feedback in distribution builds. - try { - Services.prefs.getCharPref("distribution.id"); - return; - } catch (e) {} - - let url = this._feedbackURL; - let browser = BrowserApp.selectOrAddTab(url, { parentId: BrowserApp.selectedTab.id }).browser; - - browser.addEventListener("FeedbackClose", this, false, true); - browser.addEventListener("FeedbackMaybeLater", this, false, true); - - // Dispatch a custom event to the page content when feedback is prompted by the browser. - // This will be used by the page to determine it's being loaded directly by the browser, - // instead of by the user visiting the page, e.g. through browser history. - function loadListener(event) { - browser.removeEventListener("DOMContentLoaded", loadListener, false); - browser.contentDocument.dispatchEvent(new CustomEvent("FeedbackPrompted")); - } - browser.addEventListener("DOMContentLoaded", loadListener, false); - }, - - handleEvent: function(event) { - if (!this._isAllowed(event.target)) { - return; - } - - switch (event.type) { - case "FeedbackClose": - // Do nothing. - break; - - case "FeedbackMaybeLater": - Messaging.sendRequest({ type: "Feedback:MaybeLater" }); - break; - } - - let win = event.target.ownerDocument.defaultView.top; - BrowserApp.closeTab(BrowserApp.getTabForWindow(win)); - }, - - _isAllowed: function(node) { - let uri = node.ownerDocument.documentURIObject; - let feedbackURI = Services.io.newURI(this._feedbackURL, null, null); - return uri.prePath === feedbackURI.prePath; - } -}; diff --git a/mobile/android/chrome/content/FindHelper.js b/mobile/android/chrome/content/FindHelper.js deleted file mode 100644 index 037b182d6..000000000 --- a/mobile/android/chrome/content/FindHelper.js +++ /dev/null @@ -1,197 +0,0 @@ -/* 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"; - -var FindHelper = { - _finder: null, - _targetTab: null, - _initialViewport: null, - _viewportChanged: false, - _result: null, - - // Start of nsIObserver implementation. - - observe: function(aMessage, aTopic, aData) { - switch(aTopic) { - case "FindInPage:Opened": { - this._findOpened(); - break; - } - - case "Tab:Selected": { - // Allow for page switching. - this._uninit(); - break; - } - - case "FindInPage:Closed": - this._uninit(); - this._findClosed(); - break; - } - }, - - /** - * When the FindInPageBar opens/ becomes visible, it's time to: - * 1. Add listeners for other message types sent from the FindInPageBar - * 2. initialize the Finder instance, if necessary. - */ - _findOpened: function() { - Messaging.addListener(data => this.doFind(data), "FindInPage:Find"); - Messaging.addListener(data => this.findAgain(data, false), "FindInPage:Next"); - Messaging.addListener(data => this.findAgain(data, true), "FindInPage:Prev"); - - // Initialize the finder component for the current page by performing a fake find. - this._init(); - this._finder.requestMatchesCount(""); - }, - - /** - * Fetch the Finder instance from the active tabs' browser and start tracking - * the active viewport. - */ - _init: function() { - // If there's no find in progress, start one. - if (this._finder) { - return; - } - - this._targetTab = BrowserApp.selectedTab; - try { - this._finder = this._targetTab.browser.finder; - } catch (e) { - throw new Error("FindHelper: " + e + "\n" + - "JS stack: \n" + (e.stack || Components.stack.formattedStack)); - } - - this._finder.addResultListener(this); - this._initialViewport = JSON.stringify(this._targetTab.getViewport()); - this._viewportChanged = false; - }, - - /** - * Detach from the Finder instance (so stop listening for messages) and stop - * tracking the active viewport. - */ - _uninit: function() { - // If there's no find in progress, there's nothing to clean up. - if (!this._finder) { - return; - } - - this._finder.removeSelection(); - this._finder.removeResultListener(this); - this._finder = null; - this._targetTab = null; - this._initialViewport = null; - this._viewportChanged = false; - }, - - /** - * When the FindInPageBar closes, it's time to stop listening for its messages. - */ - _findClosed: function() { - Messaging.removeListener("FindInPage:Find"); - Messaging.removeListener("FindInPage:Next"); - Messaging.removeListener("FindInPage:Prev"); - }, - - /** - * Start an asynchronous find-in-page operation, using the current Finder - * instance and request to count the amount of matches. - * If no Finder instance is currently active, we'll lazily initialize it here. - * - * @param {String} searchString Word to search for in the current document - * @return {Object} Echo of the current find action - */ - doFind: function(searchString) { - if (!this._finder) { - this._init(); - } - - this._finder.fastFind(searchString, false); - return { searchString, findBackwards: false }; - }, - - /** - * Restart the same find-in-page operation as before via `doFind()`. If we - * haven't called `doFind()`, we simply kick off a regular find. - * - * @param {String} searchString Word to search for in the current document - * @param {Boolean} findBackwards Direction to search in - * @return {Object} Echo of the current find action - */ - findAgain: function(searchString, findBackwards) { - // This always happens if the user taps next/previous after re-opening the - // search bar, and not only forces _init() but also an initial fastFind(STRING) - // before any findAgain(DIRECTION). - if (!this._finder) { - return this.doFind(searchString); - } - - this._finder.findAgain(findBackwards, false, false); - return { searchString, findBackwards }; - }, - - // Start of Finder.jsm listener implementation. - - /** - * Pass along the count results to FindInPageBar for display. The result that - * is sent to the FindInPageBar is augmented with the current find-in-page count - * limit. - * - * @param {Object} result Result coming from the Finder instance that contains - * the following properties: - * - {Number} total The total amount of matches found - * - {Number} current The index of current found range - * in the document - */ - onMatchesCountResult: function(result) { - this._result = result; - - Messaging.sendRequest(Object.assign({ - type: "FindInPage:MatchesCountResult" - }, this._result)); - }, - - /** - * When a find-in-page action finishes, this method is invoked. This is mainly - * used at the moment to detect if the current viewport has changed, which might - * be indicated by not finding a string in the current page. - * - * @param {Object} aData A dictionary, representing the find result, which - * contains the following properties: - * - {String} searchString Word that was searched for - * in the current document - * - {Number} result One of the following - * Ci.nsITypeAheadFind.* result - * indicators: FIND_FOUND, - * FIND_NOTFOUND, FIND_WRAPPED, - * FIND_PENDING - * - {Boolean} findBackwards Whether the search direction - * was backwards - * - {Boolean} findAgain Whether the previous search - * was repeated - * - {Boolean} drawOutline Whether we may (re-)draw the - * outline of a hyperlink - * - {Boolean} linksOnly Whether links-only mode was - * active - */ - onFindResult: function(aData) { - if (aData.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) { - if (this._viewportChanged) { - if (this._targetTab != BrowserApp.selectedTab) { - // this should never happen - Cu.reportError("Warning: selected tab changed during find!"); - // fall through and restore viewport on the initial tab anyway - } - this._targetTab.sendViewportUpdate(); - } - } else { - // Disabled until bug 1014113 is fixed - // ZoomHelper.zoomToRect(aData.rect); - this._viewportChanged = true; - } - } -}; diff --git a/mobile/android/chrome/content/InputWidgetHelper.js b/mobile/android/chrome/content/InputWidgetHelper.js deleted file mode 100644 index cf66a263e..000000000 --- a/mobile/android/chrome/content/InputWidgetHelper.js +++ /dev/null @@ -1,98 +0,0 @@ -/* 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"; - -var InputWidgetHelper = { - _uiBusy: false, - - handleEvent: function(aEvent) { - this.handleClick(aEvent.target); - }, - - handleClick: function(aTarget) { - // if we're busy looking at a InputWidget we want to eat any clicks that - // come to us, but not to process them - if (this._uiBusy || !this.hasInputWidget(aTarget) || this._isDisabledElement(aTarget)) - return; - - this._uiBusy = true; - this.show(aTarget); - this._uiBusy = false; - }, - - show: function(aElement) { - let type = aElement.getAttribute('type'); - let p = new Prompt({ - window: aElement.ownerDocument.defaultView, - title: Strings.browser.GetStringFromName("inputWidgetHelper." + aElement.getAttribute('type')), - buttons: [ - Strings.browser.GetStringFromName("inputWidgetHelper.set"), - Strings.browser.GetStringFromName("inputWidgetHelper.clear"), - Strings.browser.GetStringFromName("inputWidgetHelper.cancel") - ], - }).addDatePicker({ - value: aElement.value, - type: type, - min: aElement.min, - max: aElement.max, - }).show((function(data) { - let changed = false; - if (data.button == -1) { - // This type is not supported with this android version. - return; - } - if (data.button == 1) { - // The user cleared the value. - if (aElement.value != "") { - aElement.value = ""; - changed = true; - } - } else if (data.button == 0) { - // Commit the new value. - if (aElement.value != data[type]) { - aElement.value = data[type + "0"]; - changed = true; - } - } - // Else the user canceled the input. - - if (changed) - this.fireOnChange(aElement); - }).bind(this)); - }, - - hasInputWidget: function(aElement) { - if (!(aElement instanceof HTMLInputElement)) - return false; - - let type = aElement.getAttribute('type'); - if (type == "date" || type == "datetime" || type == "datetime-local" || - type == "week" || type == "month" || type == "time") { - return true; - } - - return false; - }, - - fireOnChange: function(aElement) { - let evt = aElement.ownerDocument.createEvent("Events"); - evt.initEvent("change", true, true, aElement.defaultView, 0, - false, false, - false, false, null); - setTimeout(function() { - aElement.dispatchEvent(evt); - }, 0); - }, - - _isDisabledElement : function(aElement) { - let currentElement = aElement; - while (currentElement) { - if (currentElement.disabled) - return true; - - currentElement = currentElement.parentElement; - } - return false; - } -}; diff --git a/mobile/android/chrome/content/Linkify.js b/mobile/android/chrome/content/Linkify.js deleted file mode 100644 index 3c757cc18..000000000 --- a/mobile/android/chrome/content/Linkify.js +++ /dev/null @@ -1,108 +0,0 @@ -/* 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/. */ - -const LINKIFY_TIMEOUT = 0; - -function Linkifier() { - this._linkifyTimer = null; - this._phoneRegex = /(?:\s|^)[\+]?(\(?\d{1,8}\)?)?([- ]+\(?\d{1,8}\)?)+( ?(x|ext) ?\d{1,3})?(?:\s|$)/g; -} - -Linkifier.prototype = { - _buildAnchor : function(aDoc, aNumberText) { - let anchorNode = aDoc.createElement("a"); - let cleanedText = ""; - for (let i = 0; i < aNumberText.length; i++) { - let c = aNumberText.charAt(i); - if ((c >= '0' && c <= '9') || c == '+') //assuming there is only the leading '+'. - cleanedText += c; - } - anchorNode.setAttribute("href", "tel:" + cleanedText); - let nodeText = aDoc.createTextNode(aNumberText); - anchorNode.appendChild(nodeText); - return anchorNode; - }, - - _linkifyNodeNumbers : function(aNodeToProcess, aDoc) { - let parent = aNodeToProcess.parentNode; - let nodeText = aNodeToProcess.nodeValue; - - // Replacing the original text node with a sequence of - // |text before number|anchor with number|text after number nodes. - // Each step a couple of (optional) text node and anchor node are appended. - let anchorNode = null; - let m = null; - let startIndex = 0; - let prevNode = null; - while (m = this._phoneRegex.exec(nodeText)) { - anchorNode = this._buildAnchor(aDoc, nodeText.substr(m.index, m[0].length)); - - let textExistsBeforeNumber = (m.index > startIndex); - let nodeToAdd = null; - if (textExistsBeforeNumber) - nodeToAdd = aDoc.createTextNode(nodeText.substr(startIndex, m.index - startIndex)); - else - nodeToAdd = anchorNode; - - if (!prevNode) // first time, need to replace the whole node with the first new one. - parent.replaceChild(nodeToAdd, aNodeToProcess); - else - parent.insertBefore(nodeToAdd, prevNode.nextSibling); //inserts after. - - if (textExistsBeforeNumber) // if we added the text node before the anchor, we still need to add the anchor node. - parent.insertBefore(anchorNode, nodeToAdd.nextSibling); - - // next nodes need to be appended to this node. - prevNode = anchorNode; - startIndex = m.index + m[0].length; - } - - // if some text is remaining after the last anchor. - if (startIndex > 0 && startIndex < nodeText.length) { - let lastNode = aDoc.createTextNode(nodeText.substr(startIndex)); - parent.insertBefore(lastNode, prevNode.nextSibling); - return lastNode; - } - return anchorNode; - }, - - linkifyNumbers: function(aDoc) { - // Removing any installed timer in case the page has changed and a previous timer is still running. - if (this._linkifyTimer) { - clearTimeout(this._linkifyTimer); - this._linkifyTimer = null; - } - - let filterNode = function (node) { - if (node.parentNode.tagName != 'A' && - node.parentNode.tagName != 'SCRIPT' && - node.parentNode.tagName != 'NOSCRIPT' && - node.parentNode.tagName != 'STYLE' && - node.parentNode.tagName != 'APPLET' && - node.parentNode.tagName != 'TEXTAREA') - return NodeFilter.FILTER_ACCEPT; - else - return NodeFilter.FILTER_REJECT; - } - - let nodeWalker = aDoc.createTreeWalker(aDoc.body, NodeFilter.SHOW_TEXT, filterNode, false); - let parseNode = function() { - let node = nodeWalker.nextNode(); - if (!node) { - this._linkifyTimer = null; - return; - } - let lastAddedNode = this._linkifyNodeNumbers(node, aDoc); - // we assign a different timeout whether the node was processed or not. - if (lastAddedNode) { - nodeWalker.currentNode = lastAddedNode; - this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); - } else { - this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); - } - }.bind(this); - - this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); - } -}; diff --git a/mobile/android/chrome/content/MasterPassword.js b/mobile/android/chrome/content/MasterPassword.js deleted file mode 100644 index d85fa928d..000000000 --- a/mobile/android/chrome/content/MasterPassword.js +++ /dev/null @@ -1,67 +0,0 @@ -/* 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"; - -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); - -var MasterPassword = { - pref: "privacy.masterpassword.enabled", - _tokenName: "", - - get _secModuleDB() { - delete this._secModuleDB; - return this._secModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(Ci.nsIPKCS11ModuleDB); - }, - - get _pk11DB() { - delete this._pk11DB; - return this._pk11DB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(Ci.nsIPK11TokenDB); - }, - - get enabled() { - let slot = this._secModuleDB.findSlotByName(this._tokenName); - if (slot) { - let status = slot.status; - return status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED && status != Ci.nsIPKCS11Slot.SLOT_READY; - } - return false; - }, - - setPassword: function setPassword(aPassword) { - try { - let status; - let slot = this._secModuleDB.findSlotByName(this._tokenName); - if (slot) - status = slot.status; - else - return false; - - let token = this._pk11DB.findTokenByName(this._tokenName); - - if (status == Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED) - token.initPassword(aPassword); - else if (status == Ci.nsIPKCS11Slot.SLOT_READY) - token.changePassword("", aPassword); - - return true; - } catch(e) { - dump("MasterPassword.setPassword: " + e); - } - return false; - }, - - removePassword: function removePassword(aOldPassword) { - try { - let token = this._pk11DB.getInternalKeyToken(); - if (token.checkPassword(aOldPassword)) { - token.changePassword(aOldPassword, ""); - return true; - } - } catch(e) { - dump("MasterPassword.removePassword: " + e + "\n"); - } - Snackbars.show(Strings.browser.GetStringFromName("masterPassword.incorrect"), Snackbars.LENGTH_LONG); - return false; - } -}; diff --git a/mobile/android/chrome/content/MemoryObserver.js b/mobile/android/chrome/content/MemoryObserver.js deleted file mode 100644 index 2bb3ae842..000000000 --- a/mobile/android/chrome/content/MemoryObserver.js +++ /dev/null @@ -1,88 +0,0 @@ -/* 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"; - -var MemoryObserver = { - observe: function mo_observe(aSubject, aTopic, aData) { - if (aTopic == "memory-pressure") { - if (aData != "heap-minimize") { - this.handleLowMemory(); - } - // The JS engine would normally GC on this notification, but since we - // disabled that in favor of this method (bug 669346), we should gc here. - // See bug 784040 for when this code was ported from XUL to native Fennec. - this.gc(); - } else if (aTopic == "Memory:Dump") { - this.dumpMemoryStats(aData); - } - }, - - handleLowMemory: function() { - // do things to reduce memory usage here - if (!Services.prefs.getBoolPref("browser.tabs.disableBackgroundZombification")) { - let tabs = BrowserApp.tabs; - let selected = BrowserApp.selectedTab; - for (let i = 0; i < tabs.length; i++) { - if (tabs[i] != selected && !tabs[i].playingAudio) { - this.zombify(tabs[i]); - } - } - } - - // Change some preferences temporarily for only this session - let defaults = Services.prefs.getDefaultBranch(null); - - // Reduce the amount of decoded image data we keep around - defaults.setIntPref("image.mem.max_decoded_image_kb", 0); - - // Stop using the bfcache - if (!Services.prefs.getBoolPref("browser.sessionhistory.bfcacheIgnoreMemoryPressure")) { - defaults.setIntPref("browser.sessionhistory.max_total_viewers", 0); - } - }, - - zombify: function(tab) { - let browser = tab.browser; - let data = browser.__SS_data; - let extra = browser.__SS_extdata; - - // Notify any interested parties (e.g. the session store) - // that the original tab object is going to be destroyed - let evt = document.createEvent("UIEvents"); - evt.initUIEvent("TabPreZombify", true, false, window, null); - browser.dispatchEvent(evt); - - // We need this data to correctly create and position the new browser - // If this browser is already a zombie, fallback to the session data - let currentURL = browser.__SS_restore ? data.entries[0].url : browser.currentURI.spec; - let sibling = browser.nextSibling; - let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser); - - tab.destroy(); - tab.create(currentURL, { sibling: sibling, zombifying: true, delayLoad: true, isPrivate: isPrivate }); - - // Reattach session store data and flag this browser so it is restored on select - browser = tab.browser; - browser.__SS_data = data; - browser.__SS_extdata = extra; - browser.__SS_restore = true; - browser.setAttribute("pending", "true"); - - // Notify the session store to reattach its listeners to the new tab object - evt = document.createEvent("UIEvents"); - evt.initUIEvent("TabPostZombify", true, false, window, null); - browser.dispatchEvent(evt); - }, - - gc: function() { - window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).garbageCollect(); - Cu.forceGC(); - }, - - dumpMemoryStats: function(aLabel) { - let memDumper = Cc["@mozilla.org/memory-info-dumper;1"].getService(Ci.nsIMemoryInfoDumper); - memDumper.dumpMemoryInfoToTempDir(aLabel, /* anonymize = */ false, - /* minimize = */ false); - }, -}; diff --git a/mobile/android/chrome/content/OfflineApps.js b/mobile/android/chrome/content/OfflineApps.js deleted file mode 100644 index e11b3c645..000000000 --- a/mobile/android/chrome/content/OfflineApps.js +++ /dev/null @@ -1,77 +0,0 @@ -/* 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"; - -var OfflineApps = { - offlineAppRequested: function(aContentWindow) { - if (!Services.prefs.getBoolPref("browser.offline-apps.notify")) - return; - - let tab = BrowserApp.getTabForWindow(aContentWindow); - let currentURI = aContentWindow.document.documentURIObject; - - // Don't bother showing UI if the user has already made a decision - if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION) - return; - - try { - if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) { - // All pages can use offline capabilities, no need to ask the user - return; - } - } catch(e) { - // This pref isn't set by default, ignore failures - } - - let host = currentURI.asciiHost; - let notificationID = "offline-app-requested-" + host; - - let strings = Strings.browser; - let buttons = [{ - label: strings.GetStringFromName("offlineApps.dontAllow2"), - callback: function(aChecked) { - if (aChecked) - OfflineApps.disallowSite(aContentWindow.document); - } - }, - { - label: strings.GetStringFromName("offlineApps.allow"), - callback: function() { - OfflineApps.allowSite(aContentWindow.document); - }, - positive: true - }]; - - let requestor = BrowserApp.manifest ? "'" + BrowserApp.manifest.name + "'" : host; - let message = strings.formatStringFromName("offlineApps.ask", [requestor], 1); - let options = { checkbox: Strings.browser.GetStringFromName("offlineApps.dontAskAgain") }; - NativeWindow.doorhanger.show(message, notificationID, buttons, tab.id, options); - }, - - allowSite: function(aDocument) { - Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION); - - // When a site is enabled while loading, manifest resources will - // start fetching immediately. This one time we need to do it - // ourselves. - this._startFetching(aDocument); - }, - - disallowSite: function(aDocument) { - Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION); - }, - - _startFetching: function(aDocument) { - if (!aDocument.documentElement) - return; - - let manifest = aDocument.documentElement.getAttribute("manifest"); - if (!manifest) - return; - - let manifestURI = Services.io.newURI(manifest, aDocument.characterSet, aDocument.documentURIObject); - let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(Ci.nsIOfflineCacheUpdateService); - updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, aDocument.nodePrincipal, window); - } -}; diff --git a/mobile/android/chrome/content/PermissionsHelper.js b/mobile/android/chrome/content/PermissionsHelper.js deleted file mode 100644 index ad1eb760a..000000000 --- a/mobile/android/chrome/content/PermissionsHelper.js +++ /dev/null @@ -1,188 +0,0 @@ -/* 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"; - -var PermissionsHelper = { - _permissonTypes: ["password", "geolocation", "popup", "indexedDB", - "offline-app", "desktop-notification", "plugins", "native-intent", - "flyweb-publish-server"], - _permissionStrings: { - "password": { - label: "password.logins", - allowed: "password.save", - denied: "password.dontSave" - }, - "geolocation": { - label: "geolocation.location", - allowed: "geolocation.allow", - denied: "geolocation.dontAllow" - }, - "flyweb-publish-server": { - label: "flyWebPublishServer.publishServer", - allowed: "flyWebPublishServer.allow", - denied: "flyWebPublishServer.dontAllow" - }, - "popup": { - label: "blockPopups.label2", - allowed: "popup.show", - denied: "popup.dontShow" - }, - "indexedDB": { - label: "offlineApps.offlineData", - allowed: "offlineApps.allow", - denied: "offlineApps.dontAllow2" - }, - "offline-app": { - label: "offlineApps.offlineData", - allowed: "offlineApps.allow", - denied: "offlineApps.dontAllow2" - }, - "desktop-notification": { - label: "desktopNotification.notifications", - allowed: "desktopNotification2.allow", - denied: "desktopNotification2.dontAllow" - }, - "plugins": { - label: "clickToPlayPlugins.plugins", - allowed: "clickToPlayPlugins.activate", - denied: "clickToPlayPlugins.dontActivate" - }, - "native-intent": { - label: "helperapps.openWithList2", - allowed: "helperapps.always", - denied: "helperapps.never" - } - }, - - observe: function observe(aSubject, aTopic, aData) { - let uri = BrowserApp.selectedBrowser.currentURI; - let check = false; - - switch (aTopic) { - case "Permissions:Check": - check = true; - case "Permissions:Get": - let permissions = []; - for (let i = 0; i < this._permissonTypes.length; i++) { - let type = this._permissonTypes[i]; - let value = this.getPermission(uri, type); - - // Only add the permission if it was set by the user - if (value == Services.perms.UNKNOWN_ACTION) - continue; - - if (check) { - Messaging.sendRequest({ - type: "Permissions:CheckResult", - hasPermissions: true - }); - return; - } - // Get the strings that correspond to the permission type - let typeStrings = this._permissionStrings[type]; - let label = Strings.browser.GetStringFromName(typeStrings["label"]); - - // Get the key to look up the appropriate string entity - let valueKey = value == Services.perms.ALLOW_ACTION ? - "allowed" : "denied"; - let valueString = Strings.browser.GetStringFromName(typeStrings[valueKey]); - - permissions.push({ - type: type, - setting: label, - value: valueString - }); - } - - if (check) { - Messaging.sendRequest({ - type: "Permissions:CheckResult", - hasPermissions: false - }); - return; - } - - // Keep track of permissions, so we know which ones to clear - this._currentPermissions = permissions; - - Messaging.sendRequest({ - type: "Permissions:Data", - permissions: permissions - }); - break; - - case "Permissions:Clear": - // An array of the indices of the permissions we want to clear - let permissionsToClear = JSON.parse(aData); - let privacyContext = BrowserApp.selectedBrowser.docShell - .QueryInterface(Ci.nsILoadContext); - - for (let i = 0; i < permissionsToClear.length; i++) { - let indexToClear = permissionsToClear[i]; - let permissionType = this._currentPermissions[indexToClear]["type"]; - this.clearPermission(uri, permissionType, privacyContext); - } - break; - } - }, - - /** - * Gets the permission value stored for a specified permission type. - * - * @param aType - * The permission type string stored in permission manager. - * e.g. "geolocation", "indexedDB", "popup" - * - * @return A permission value defined in nsIPermissionManager. - */ - getPermission: function getPermission(aURI, aType) { - // Password saving isn't a nsIPermissionManager permission type, so handle - // it seperately. - if (aType == "password") { - // By default, login saving is enabled, so if it is disabled, the - // user selected the never remember option - if (!Services.logins.getLoginSavingEnabled(aURI.prePath)) - return Services.perms.DENY_ACTION; - - // Check to see if the user ever actually saved a login - if (Services.logins.countLogins(aURI.prePath, "", "")) - return Services.perms.ALLOW_ACTION; - - return Services.perms.UNKNOWN_ACTION; - } - - // Geolocation consumers use testExactPermission - if (aType == "geolocation") - return Services.perms.testExactPermission(aURI, aType); - - return Services.perms.testPermission(aURI, aType); - }, - - /** - * Clears a user-set permission value for the site given a permission type. - * - * @param aType - * The permission type string stored in permission manager. - * e.g. "geolocation", "indexedDB", "popup" - */ - clearPermission: function clearPermission(aURI, aType, aContext) { - // Password saving isn't a nsIPermissionManager permission type, so handle - // it seperately. - if (aType == "password") { - // Get rid of exisiting stored logings - let logins = Services.logins.findLogins({}, aURI.prePath, "", ""); - for (let i = 0; i < logins.length; i++) { - Services.logins.removeLogin(logins[i]); - } - // Re-set login saving to enabled - Services.logins.setLoginSavingEnabled(aURI.prePath, true); - } else { - Services.perms.remove(aURI, aType); - // Clear content prefs set in ContentPermissionPrompt.js - Cc["@mozilla.org/content-pref/service;1"] - .getService(Ci.nsIContentPrefService2) - .removeByDomainAndName(aURI.spec, aType + ".request.remember", aContext); - } - } -}; diff --git a/mobile/android/chrome/content/PluginHelper.js b/mobile/android/chrome/content/PluginHelper.js deleted file mode 100644 index 59d87fa7c..000000000 --- a/mobile/android/chrome/content/PluginHelper.js +++ /dev/null @@ -1,221 +0,0 @@ -/* 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"; - -var PluginHelper = { - showDoorHanger: function(aTab) { - if (!aTab.browser) - return; - - // Even though we may not end up showing a doorhanger, this flag - // lets us know that we've tried to show a doorhanger. - aTab.shouldShowPluginDoorhanger = false; - - let uri = aTab.browser.currentURI; - - // If the user has previously set a plugins permission for this website, - // either play or don't play the plugins instead of showing a doorhanger. - let permValue = Services.perms.testPermission(uri, "plugins"); - if (permValue != Services.perms.UNKNOWN_ACTION) { - if (permValue == Services.perms.ALLOW_ACTION) - PluginHelper.playAllPlugins(aTab.browser.contentWindow); - - return; - } - - let message = Strings.browser.formatStringFromName("clickToPlayPlugins.message2", - [uri.host], 1); - let buttons = [ - { - label: Strings.browser.GetStringFromName("clickToPlayPlugins.dontActivate"), - callback: function(aChecked) { - // If the user checked "Don't ask again", make a permanent exception - if (aChecked) - Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.DENY_ACTION); - - // Other than that, do nothing - } - }, - { - label: Strings.browser.GetStringFromName("clickToPlayPlugins.activate"), - callback: function(aChecked) { - // If the user checked "Don't ask again", make a permanent exception - if (aChecked) - Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION); - - PluginHelper.playAllPlugins(aTab.browser.contentWindow); - }, - positive: true - } - ]; - - // Add a checkbox with a "Don't ask again" message if the uri contains a - // host. Adding a permanent exception will fail if host is not present. - let options = uri.host ? { checkbox: Strings.browser.GetStringFromName("clickToPlayPlugins.dontAskAgain") } : {}; - - NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id, options); - }, - - delayAndShowDoorHanger: function(aTab) { - // To avoid showing the doorhanger if there are also visible plugin - // overlays on the page, delay showing the doorhanger to check if - // visible plugins get added in the near future. - if (!aTab.pluginDoorhangerTimeout) { - aTab.pluginDoorhangerTimeout = setTimeout(function() { - if (this.shouldShowPluginDoorhanger) { - PluginHelper.showDoorHanger(this); - } - }.bind(aTab), 500); - } - }, - - playAllPlugins: function(aContentWindow) { - let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - // XXX not sure if we should enable plugins for the parent documents... - let plugins = cwu.plugins; - if (!plugins || !plugins.length) - return; - - plugins.forEach(this.playPlugin); - }, - - playPlugin: function(plugin) { - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (!objLoadingContent.activated) - objLoadingContent.playPlugin(); - }, - - getPluginPreference: function getPluginPreference() { - let pluginDisable = Services.prefs.getBoolPref("plugin.disable"); - if (pluginDisable) - return "0"; - - let state = Services.prefs.getIntPref("plugin.default.state"); - return state == Ci.nsIPluginTag.STATE_CLICKTOPLAY ? "2" : "1"; - }, - - setPluginPreference: function setPluginPreference(aValue) { - switch (aValue) { - case "0": // Enable Plugins = No - Services.prefs.setBoolPref("plugin.disable", true); - Services.prefs.clearUserPref("plugin.default.state"); - break; - case "1": // Enable Plugins = Yes - Services.prefs.clearUserPref("plugin.disable"); - Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED); - break; - case "2": // Enable Plugins = Tap to Play (default) - Services.prefs.clearUserPref("plugin.disable"); - Services.prefs.clearUserPref("plugin.default.state"); - break; - } - }, - - // Copied from /browser/base/content/browser.js - isTooSmall : function (plugin, overlay) { - // Is the <object>'s size too small to hold what we want to show? - let pluginRect = plugin.getBoundingClientRect(); - // XXX bug 446693. The text-shadow on the submitted-report text at - // the bottom causes scrollHeight to be larger than it should be. - let overflows = (overlay.scrollWidth > pluginRect.width) || - (overlay.scrollHeight - 5 > pluginRect.height); - - return overflows; - }, - - getPluginMimeType: function (plugin) { - var tagMimetype = plugin.actualType; - - if (tagMimetype == "") { - tagMimetype = plugin.type; - } - - return tagMimetype; - }, - - handlePluginBindingAttached: function (aTab, aEvent) { - let plugin = aEvent.target; - let doc = plugin.ownerDocument; - let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); - if (!overlay || overlay._bindingHandled) { - return; - } - overlay._bindingHandled = true; - - let eventType = PluginHelper._getBindingType(plugin); - if (!eventType) { - // Not all bindings have handlers - return; - } - - switch (eventType) { - case "PluginClickToPlay": { - // Check if plugins have already been activated for this page, or if - // the user has set a permission to always play plugins on the site - if (aTab.clickToPlayPluginsActivated || - Services.perms.testPermission(aTab.browser.currentURI, "plugins") == - Services.perms.ALLOW_ACTION) { - PluginHelper.playPlugin(plugin); - return; - } - - // If the plugin is hidden, or if the overlay is too small, show a - // doorhanger notification - if (PluginHelper.isTooSmall(plugin, overlay)) { - PluginHelper.delayAndShowDoorHanger(aTab); - } else { - // There's a large enough visible overlay that we don't need to show - // the doorhanger. - aTab.shouldShowPluginDoorhanger = false; - overlay.classList.add("visible"); - } - - // Add click to play listener to the overlay - overlay.addEventListener("click", function(e) { - if (!e.isTrusted) - return; - e.preventDefault(); - let win = e.target.ownerDocument.defaultView.top; - let tab = BrowserApp.getTabForWindow(win); - tab.clickToPlayPluginsActivated = true; - PluginHelper.playAllPlugins(win); - - NativeWindow.doorhanger.hide("ask-to-play-plugins", tab.id); - }, true); - - // Add handlers for over- and underflow in case the plugin gets resized - plugin.addEventListener("overflow", function(event) { - overlay.classList.remove("visible"); - PluginHelper.delayAndShowDoorHanger(aTab); - }); - plugin.addEventListener("underflow", function(event) { - // This is also triggered if only one dimension underflows, - // the other dimension might still overflow - if (!PluginHelper.isTooSmall(plugin, overlay)) { - overlay.classList.add("visible"); - } - }); - - break; - } - } - }, - - // Helper to get the binding handler type from a plugin object - _getBindingType: function(plugin) { - if (!(plugin instanceof Ci.nsIObjectLoadingContent)) - return null; - - switch (plugin.pluginFallbackType) { - case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: - return "PluginNotFound"; - case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: - return "PluginClickToPlay"; - default: - // Not all states map to a handler - return null; - } - } -}; diff --git a/mobile/android/chrome/content/PresentationView.js b/mobile/android/chrome/content/PresentationView.js deleted file mode 100644 index 4f7e02870..000000000 --- a/mobile/android/chrome/content/PresentationView.js +++ /dev/null @@ -1,63 +0,0 @@ -/* -*- Mode: tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* 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"; - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -const TOPIC_PRESENTATION_VIEW_READY = "presentation-view-ready"; -const TOPIC_PRESENTATION_RECEIVER_LAUNCH = "presentation-receiver:launch"; -const TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE = "presentation-receiver:launch:response"; - -// globals Services -Cu.import("resource://gre/modules/Services.jsm"); - -function log(str) { - // dump("-*- PresentationView.js -*-: " + str + "\n"); -} - -let PresentationView = { - _id: null, - - startup: function startup() { - // use hash as the ID of this top level window - this._id = window.location.hash.substr(1); - - // Listen "presentation-receiver:launch" sent from - // PresentationRequestUIGlue. - Services.obs.addObserver(this,TOPIC_PRESENTATION_RECEIVER_LAUNCH, false); - - // Notify PresentationView is ready. - Services.obs.notifyObservers(null, TOPIC_PRESENTATION_VIEW_READY, this._id); - }, - - stop: function stop() { - Services.obs.removeObserver(this, TOPIC_PRESENTATION_RECEIVER_LAUNCH); - }, - - observe: function observe(aSubject, aTopic, aData) { - log("Got observe: aTopic=" + aTopic); - - let requestData = JSON.parse(aData); - if (this._id != requestData.windowId) { - return; - } - - let browser = document.getElementById("content"); - browser.setAttribute("mozpresentation", requestData.url); - try { - browser.loadURI(requestData.url); - Services.obs.notifyObservers(browser, - TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE, - JSON.stringify({ result: "success", - requestId: requestData.requestId })); - } catch (e) { - Services.obs.notifyObservers(null, - TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE, - JSON.stringify({ result: "error", - reason: e.message })); - } - } -}; diff --git a/mobile/android/chrome/content/PresentationView.xul b/mobile/android/chrome/content/PresentationView.xul deleted file mode 100644 index 00440453c..000000000 --- a/mobile/android/chrome/content/PresentationView.xul +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> - -<window id="presentation-window" - onload="PresentationView.startup();" - onunload="PresentationView.stop();" - windowtype="navigator:browser" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <browser id="content" type="content-targetable" src="about:blank" flex="1"/> - - <script type="application/javascript" src="chrome://browser/content/PresentationView.js"/> -</window> diff --git a/mobile/android/chrome/content/PrintHelper.js b/mobile/android/chrome/content/PrintHelper.js deleted file mode 100644 index 9b071ee92..000000000 --- a/mobile/android/chrome/content/PrintHelper.js +++ /dev/null @@ -1,73 +0,0 @@ -// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- -/* 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"; - -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); - -var PrintHelper = { - init: function() { - Services.obs.addObserver(this, "Print:PDF", false); - }, - - observe: function (aSubject, aTopic, aData) { - let browser = BrowserApp.selectedBrowser; - - switch (aTopic) { - case "Print:PDF": - Messaging.handleRequest(aTopic, aData, (data) => { - return this.generatePDF(browser); - }); - break; - } - }, - - generatePDF: function(aBrowser) { - // Create the final destination file location - let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null); - fileName = fileName.trim() + ".pdf"; - - let file = Services.dirsvc.get("TmpD", Ci.nsIFile); - file.append(fileName); - file.createUnique(file.NORMAL_FILE_TYPE, parseInt("666", 8)); - - let printSettings = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService).newPrintSettings; - printSettings.printSilent = true; - printSettings.showPrintProgress = false; - printSettings.printBGImages = false; - printSettings.printBGColors = false; - printSettings.printToFile = true; - printSettings.toFileName = file.path; - printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs; - printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; - - let webBrowserPrint = aBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebBrowserPrint); - - return new Promise((resolve, reject) => { - webBrowserPrint.print(printSettings, { - onStateChange: function(webProgress, request, stateFlags, status) { - // We get two STATE_START calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK - if (stateFlags & Ci.nsIWebProgressListener.STATE_START && stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { - // Let the user know something is happening. Generating the PDF can take some time. - Snackbars.show(Strings.browser.GetStringFromName("alertPrintjobToast"), Snackbars.LENGTH_LONG); - } - - // We get two STATE_STOP calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK - if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP && stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { - if (Components.isSuccessCode(status)) { - // Send the details to Java - resolve({ file: file.path, title: fileName }); - } else { - reject(); - } - } - }, - onProgressChange: function () {}, - onLocationChange: function () {}, - onStatusChange: function () {}, - onSecurityChange: function () {}, - }); - }); - } -}; diff --git a/mobile/android/chrome/content/Reader.js b/mobile/android/chrome/content/Reader.js deleted file mode 100644 index d0f3d7801..000000000 --- a/mobile/android/chrome/content/Reader.js +++ /dev/null @@ -1,290 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- -/* 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"; - -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); - -/*globals MAX_URI_LENGTH, MAX_TITLE_LENGTH */ - -var Reader = { - // These values should match those defined in BrowserContract.java. - STATUS_UNFETCHED: 0, - STATUS_FETCH_FAILED_TEMPORARY: 1, - STATUS_FETCH_FAILED_PERMANENT: 2, - STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT: 3, - STATUS_FETCHED_ARTICLE: 4, - - get _hasUsedToolbar() { - delete this._hasUsedToolbar; - return this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar"); - }, - - /** - * BackPressListener (listeners / ReaderView Ids). - */ - _backPressListeners: [], - _backPressViewIds: [], - - /** - * Set a backPressListener for this tabId / ReaderView Id pair. - */ - _addBackPressListener: function(tabId, viewId, listener) { - this._backPressListeners[tabId] = listener; - this._backPressViewIds[viewId] = tabId; - }, - - /** - * Remove a backPressListener for this ReaderView Id. - */ - _removeBackPressListener: function(viewId) { - let tabId = this._backPressViewIds[viewId]; - if (tabId != undefined) { - this._backPressListeners[tabId] = null; - delete this._backPressViewIds[viewId]; - } - }, - - /** - * If the requested tab has a backPress listener, return its results, else false. - */ - onBackPress: function(tabId) { - let listener = this._backPressListeners[tabId]; - return { handled: (listener ? listener() : false) }; - }, - - observe: function Reader_observe(aMessage, aTopic, aData) { - switch (aTopic) { - case "Reader:RemoveFromCache": { - ReaderMode.removeArticleFromCache(aData).catch(e => Cu.reportError("Error removing article from cache: " + e)); - break; - } - - case "Reader:AddToCache": { - let tab = BrowserApp.getTabForId(aData); - if (!tab) { - throw new Error("No tab for tabID = " + aData + " when trying to save reader view article"); - } - - // If the article is coming from reader mode, we must have fetched it already. - this._getArticleData(tab.browser).then((article) => { - ReaderMode.storeArticleInCache(article); - }).catch(e => Cu.reportError("Error storing article in cache: " + e)); - break; - } - } - }, - - receiveMessage: function(message) { - switch (message.name) { - case "Reader:ArticleGet": - this._getArticle(message.data.url).then((article) => { - // Make sure the target browser is still alive before trying to send data back. - if (message.target.messageManager) { - message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article }); - } - }, e => { - if (e && e.newURL) { - message.target.loadURI("about:reader?url=" + encodeURIComponent(e.newURL)); - } - }); - break; - - // On DropdownClosed in ReaderView, we cleanup / clear existing BackPressListener. - case "Reader:DropdownClosed": { - this._removeBackPressListener(message.data); - break; - } - - // On DropdownOpened in ReaderView, we add BackPressListener to handle a subsequent BACK request. - case "Reader:DropdownOpened": { - let tabId = BrowserApp.selectedTab.id; - this._addBackPressListener(tabId, message.data, () => { - // User hit BACK key while ReaderView has the banner font-dropdown opened. - // Close it and return prevent-default. - if (message.target.messageManager) { - message.target.messageManager.sendAsyncMessage("Reader:CloseDropdown"); - return true; - } - // We can assume ReaderView banner's font-dropdown doesn't need to be closed. - return false; - }); - - break; - } - - case "Reader:FaviconRequest": { - Messaging.sendRequestForResult({ - type: "Reader:FaviconRequest", - url: message.data.url - }).then(data => { - message.target.messageManager.sendAsyncMessage("Reader:FaviconReturn", JSON.parse(data)); - }); - break; - } - - case "Reader:SystemUIVisibility": - Messaging.sendRequest({ - type: "SystemUI:Visibility", - visible: message.data.visible - }); - break; - - case "Reader:ToolbarHidden": - if (!this._hasUsedToolbar) { - Snackbars.show(Strings.browser.GetStringFromName("readerMode.toolbarTip"), Snackbars.LENGTH_LONG); - Services.prefs.setBoolPref("reader.has_used_toolbar", true); - this._hasUsedToolbar = true; - } - break; - - case "Reader:UpdateReaderButton": { - let tab = BrowserApp.getTabForBrowser(message.target); - tab.browser.isArticle = message.data.isArticle; - this.updatePageAction(tab); - break; - } - } - }, - - pageAction: { - readerModeCallback: function(browser) { - let url = browser.currentURI.spec; - if (url.startsWith("about:reader")) { - UITelemetry.addEvent("action.1", "button", null, "reader_exit"); - } else { - UITelemetry.addEvent("action.1", "button", null, "reader_enter"); - } - browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode"); - }, - }, - - updatePageAction: function(tab) { - if (!tab.getActive()) { - return; - } - - if (this.pageAction.id) { - PageActions.remove(this.pageAction.id); - delete this.pageAction.id; - } - - let showPageAction = (icon, title) => { - this.pageAction.id = PageActions.add({ - icon: icon, - title: title, - clickCallback: () => this.pageAction.readerModeCallback(browser), - important: true - }); - }; - - let browser = tab.browser; - if (browser.currentURI.spec.startsWith("about:reader")) { - showPageAction("drawable://reader_active", Strings.reader.GetStringFromName("readerView.close")); - // Only start a reader session if the viewer is in the foreground. We do - // not track background reader viewers. - UITelemetry.startSession("reader.1", null); - return; - } - - // Only stop a reader session if the foreground viewer is not visible. - UITelemetry.stopSession("reader.1", "", null); - - if (browser.isArticle) { - showPageAction("drawable://reader", Strings.reader.GetStringFromName("readerView.enter")); - UITelemetry.addEvent("show.1", "button", null, "reader_available"); - } else { - UITelemetry.addEvent("show.1", "button", null, "reader_unavailable"); - } - }, - - /** - * Gets an article for a given URL. This method will download and parse a document - * if it does not find the article in the cache. - * - * @param url The article URL. - * @return {Promise} - * @resolves JS object representing the article, or null if no article is found. - */ - _getArticle: Task.async(function* (url) { - // First try to find a parsed article in the cache. - let article = yield ReaderMode.getArticleFromCache(url); - if (article) { - return article; - } - - // Article hasn't been found in the cache, we need to - // download the page and parse the article out of it. - return yield ReaderMode.downloadAndParseDocument(url).catch(e => { - if (e && e.newURL) { - // Pass up the error so we can navigate the browser in question to the new URL: - throw e; - } - Cu.reportError("Error downloading and parsing document: " + e); - return null; - }); - }), - - _getArticleData: function(browser) { - return new Promise((resolve, reject) => { - if (browser == null) { - reject("_getArticleData needs valid browser"); - } - - let mm = browser.messageManager; - let listener = (message) => { - mm.removeMessageListener("Reader:StoredArticleData", listener); - resolve(message.data.article); - }; - mm.addMessageListener("Reader:StoredArticleData", listener); - mm.sendAsyncMessage("Reader:GetStoredArticleData"); - }); - }, - - - /** - * Migrates old indexedDB reader mode cache to new JSON cache. - */ - migrateCache: Task.async(function* () { - let cacheDB = yield new Promise((resolve, reject) => { - let request = window.indexedDB.open("about:reader", 1); - request.onsuccess = event => resolve(event.target.result); - request.onerror = event => reject(request.error); - - // If there is no DB to migrate, don't do anything. - request.onupgradeneeded = event => resolve(null); - }); - - if (!cacheDB) { - return; - } - - let articles = yield new Promise((resolve, reject) => { - let articles = []; - - let transaction = cacheDB.transaction(cacheDB.objectStoreNames); - let store = transaction.objectStore(cacheDB.objectStoreNames[0]); - - let request = store.openCursor(); - request.onsuccess = event => { - let cursor = event.target.result; - if (!cursor) { - resolve(articles); - } else { - articles.push(cursor.value); - cursor.continue(); - } - }; - request.onerror = event => reject(request.error); - }); - - for (let article of articles) { - yield ReaderMode.storeArticleInCache(article); - } - - // Delete the database. - window.indexedDB.deleteDatabase("about:reader"); - }), -}; diff --git a/mobile/android/chrome/content/RemoteDebugger.js b/mobile/android/chrome/content/RemoteDebugger.js deleted file mode 100644 index a5a3a43de..000000000 --- a/mobile/android/chrome/content/RemoteDebugger.js +++ /dev/null @@ -1,355 +0,0 @@ -// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- -/* 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/. */ -/* globals DebuggerServer */ -"use strict"; - -XPCOMUtils.defineLazyGetter(this, "DebuggerServer", () => { - let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); - let { DebuggerServer } = require("devtools/server/main"); - return DebuggerServer; -}); - -var RemoteDebugger = { - init() { - USBRemoteDebugger.init(); - WiFiRemoteDebugger.init(); - }, - - get isAnyEnabled() { - return USBRemoteDebugger.isEnabled || WiFiRemoteDebugger.isEnabled; - }, - - /** - * Prompt the user to accept or decline the incoming connection. - * - * @param session object - * The session object will contain at least the following fields: - * { - * authentication, - * client: { - * host, - * port - * }, - * server: { - * host, - * port - * } - * } - * Specific authentication modes may include additional fields. Check - * the different |allowConnection| methods in - * devtools/shared/security/auth.js. - * @return An AuthenticationResult value. - * A promise that will be resolved to the above is also allowed. - */ - allowConnection(session) { - if (this._promptingForAllow) { - // Don't stack connection prompts if one is already open - return DebuggerServer.AuthenticationResult.DENY; - } - - if (!session.server.port) { - this._promptingForAllow = this._promptForUSB(session); - } else { - this._promptingForAllow = this._promptForTCP(session); - } - this._promptingForAllow.then(() => this._promptingForAllow = null); - - return this._promptingForAllow; - }, - - _promptForUSB(session) { - if (session.authentication !== 'PROMPT') { - // This dialog is not prepared for any other authentication method at - // this time. - return DebuggerServer.AuthenticationResult.DENY; - } - - return new Promise(resolve => { - let title = Strings.browser.GetStringFromName("remoteIncomingPromptTitle"); - let msg = Strings.browser.GetStringFromName("remoteIncomingPromptUSB"); - let allow = Strings.browser.GetStringFromName("remoteIncomingPromptAllow"); - let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny"); - - // Make prompt. Note: button order is in reverse. - let prompt = new Prompt({ - window: null, - hint: "remotedebug", - title: title, - message: msg, - buttons: [ allow, deny ], - priority: 1 - }); - - prompt.show(data => { - let result = data.button; - if (result === 0) { - resolve(DebuggerServer.AuthenticationResult.ALLOW); - } else { - resolve(DebuggerServer.AuthenticationResult.DENY); - } - }); - }); - }, - - _promptForTCP(session) { - if (session.authentication !== 'OOB_CERT' || !session.client.cert) { - // This dialog is not prepared for any other authentication method at - // this time. - return DebuggerServer.AuthenticationResult.DENY; - } - - return new Promise(resolve => { - let title = Strings.browser.GetStringFromName("remoteIncomingPromptTitle"); - let msg = Strings.browser.formatStringFromName("remoteIncomingPromptTCP", [ - session.client.host, - session.client.port - ], 2); - let scan = Strings.browser.GetStringFromName("remoteIncomingPromptScan"); - let scanAndRemember = Strings.browser.GetStringFromName("remoteIncomingPromptScanAndRemember"); - let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny"); - - // Make prompt. Note: button order is in reverse. - let prompt = new Prompt({ - window: null, - hint: "remotedebug", - title: title, - message: msg, - buttons: [ scan, scanAndRemember, deny ], - priority: 1 - }); - - prompt.show(data => { - let result = data.button; - if (result === 0) { - resolve(DebuggerServer.AuthenticationResult.ALLOW); - } else if (result === 1) { - resolve(DebuggerServer.AuthenticationResult.ALLOW_PERSIST); - } else { - resolve(DebuggerServer.AuthenticationResult.DENY); - } - }); - }); - }, - - /** - * During OOB_CERT authentication, the user must transfer some data through - * some out of band mechanism from the client to the server to authenticate - * the devices. - * - * This implementation instructs Fennec to invoke a QR decoder and return the - * the data it contains back here. - * - * @return An object containing: - * * sha256: hash(ClientCert) - * * k : K(random 128-bit number) - * A promise that will be resolved to the above is also allowed. - */ - receiveOOB() { - if (this._receivingOOB) { - return this._receivingOOB; - } - - this._receivingOOB = Messaging.sendRequestForResult({ - type: "DevToolsAuth:Scan" - }).then(data => { - return JSON.parse(data); - }, () => { - let title = Strings.browser.GetStringFromName("remoteQRScanFailedPromptTitle"); - let msg = Strings.browser.GetStringFromName("remoteQRScanFailedPromptMessage"); - let ok = Strings.browser.GetStringFromName("remoteQRScanFailedPromptOK"); - let prompt = new Prompt({ - window: null, - hint: "remotedebug", - title: title, - message: msg, - buttons: [ ok ], - priority: 1 - }); - prompt.show(); - }); - - this._receivingOOB.then(() => this._receivingOOB = null); - - return this._receivingOOB; - }, - - initServer: function() { - if (DebuggerServer.initialized) { - return; - } - - DebuggerServer.init(); - - // Add browser and Fennec specific actors - DebuggerServer.addBrowserActors(); - DebuggerServer.registerModule("resource://gre/modules/dbg-browser-actors.js"); - - // Allow debugging of chrome for any process - DebuggerServer.allowChromeProcess = true; - } -}; - -RemoteDebugger.allowConnection = - RemoteDebugger.allowConnection.bind(RemoteDebugger); -RemoteDebugger.receiveOOB = - RemoteDebugger.receiveOOB.bind(RemoteDebugger); - -var USBRemoteDebugger = { - - init() { - Services.prefs.addObserver("devtools.", this, false); - - if (this.isEnabled) { - this.start(); - } - }, - - observe(subject, topic, data) { - if (topic != "nsPref:changed") { - return; - } - - switch (data) { - case "devtools.remote.usb.enabled": - Services.prefs.setBoolPref("devtools.debugger.remote-enabled", - RemoteDebugger.isAnyEnabled); - if (this.isEnabled) { - this.start(); - } else { - this.stop(); - } - break; - - case "devtools.debugger.remote-port": - case "devtools.debugger.unix-domain-socket": - if (this.isEnabled) { - this.stop(); - this.start(); - } - break; - } - }, - - get isEnabled() { - return Services.prefs.getBoolPref("devtools.remote.usb.enabled"); - }, - - start: function() { - if (this._listener) { - return; - } - - RemoteDebugger.initServer(); - - let portOrPath = - Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || - Services.prefs.getIntPref("devtools.debugger.remote-port"); - - try { - dump("Starting USB debugger on " + portOrPath); - let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT"); - let authenticator = new AuthenticatorType.Server(); - authenticator.allowConnection = RemoteDebugger.allowConnection; - this._listener = DebuggerServer.createListener(); - this._listener.portOrPath = portOrPath; - this._listener.authenticator = authenticator; - this._listener.open(); - } catch (e) { - dump("Unable to start USB debugger server: " + e); - } - }, - - stop: function() { - if (!this._listener) { - return; - } - - try { - this._listener.close(); - this._listener = null; - } catch (e) { - dump("Unable to stop USB debugger server: " + e); - } - } - -}; - -var WiFiRemoteDebugger = { - - init() { - Services.prefs.addObserver("devtools.", this, false); - - if (this.isEnabled) { - this.start(); - } - }, - - observe(subject, topic, data) { - if (topic != "nsPref:changed") { - return; - } - - switch (data) { - case "devtools.remote.wifi.enabled": - Services.prefs.setBoolPref("devtools.debugger.remote-enabled", - RemoteDebugger.isAnyEnabled); - // Allow remote debugging on non-local interfaces when WiFi debug is - // enabled - // TODO: Bug 1034411: Lock down to WiFi interface only - Services.prefs.setBoolPref("devtools.debugger.force-local", - !this.isEnabled); - if (this.isEnabled) { - this.start(); - } else { - this.stop(); - } - break; - } - }, - - get isEnabled() { - return Services.prefs.getBoolPref("devtools.remote.wifi.enabled"); - }, - - start: function() { - if (this._listener) { - return; - } - - RemoteDebugger.initServer(); - - try { - dump("Starting WiFi debugger"); - let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT"); - let authenticator = new AuthenticatorType.Server(); - authenticator.allowConnection = RemoteDebugger.allowConnection; - authenticator.receiveOOB = RemoteDebugger.receiveOOB; - this._listener = DebuggerServer.createListener(); - this._listener.portOrPath = -1 /* any available port */; - this._listener.authenticator = authenticator; - this._listener.discoverable = true; - this._listener.encryption = true; - this._listener.open(); - let port = this._listener.port; - dump("Started WiFi debugger on " + port); - } catch (e) { - dump("Unable to start WiFi debugger server: " + e); - } - }, - - stop: function() { - if (!this._listener) { - return; - } - - try { - this._listener.close(); - this._listener = null; - } catch (e) { - dump("Unable to stop WiFi debugger server: " + e); - } - } - -}; diff --git a/mobile/android/chrome/content/SelectHelper.js b/mobile/android/chrome/content/SelectHelper.js deleted file mode 100644 index 41d0193d4..000000000 --- a/mobile/android/chrome/content/SelectHelper.js +++ /dev/null @@ -1,161 +0,0 @@ -/* 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"; - -var SelectHelper = { - _uiBusy: false, - - handleEvent: function(event) { - this.handleClick(event.target); - }, - - handleClick: function(target) { - // if we're busy looking at a select we want to eat any clicks that - // come to us, but not to process them - if (this._uiBusy || !this._isMenu(target) || this._isDisabledElement(target)) { - return; - } - - this._uiBusy = true; - this.show(target); - this._uiBusy = false; - }, - - // This is a callback function to be provided to prompt.show(callBack). - // It will update which Option elements in a Select have been selected - // or unselected and fire the onChange event. - _promptCallBack: function(data, element) { - let selected = data.list; - - if (element instanceof Ci.nsIDOMXULMenuListElement) { - if (element.selectedIndex != selected[0]) { - element.selectedIndex = selected[0]; - this.fireOnCommand(element); - } - } else if (element instanceof HTMLSelectElement) { - let changed = false; - let i = 0; // The index for the element from `data.list` that we are currently examining. - this.forVisibleOptions(element, function(node) { - if (node.selected && selected.indexOf(i) == -1) { - changed = true; - node.selected = false; - } else if (!node.selected && selected.indexOf(i) != -1) { - changed = true; - node.selected = true; - } - i++; - }); - - if (changed) { - this.fireOnChange(element); - } - } - }, - - show: function(element) { - let list = this.getListForElement(element); - let p = new Prompt({ - window: element.ownerDocument.defaultView - }); - - if (element.multiple) { - p.addButton({ - label: Strings.browser.GetStringFromName("selectHelper.closeMultipleSelectDialog") - }).setMultiChoiceItems(list); - } else { - p.setSingleChoiceItems(list); - } - - p.show((data) => { - this._promptCallBack(data,element) - }); - }, - - _isMenu: function(element) { - return (element instanceof HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement); - }, - - // Return a list of Option elements within a Select excluding - // any that were not visible. - getListForElement: function(element) { - let index = 0; - let items = []; - this.forVisibleOptions(element, function(node, options,parent) { - let item = { - label: node.text || node.label, - header: options.isGroup, - disabled: node.disabled, - id: index, - selected: node.selected, - }; - - if (parent) { - item.child = true; - item.disabled = item.disabled || parent.disabled; - } - items.push(item); - index++; - }); - return items; - }, - - // Apply a function to all visible Option elements in a Select - forVisibleOptions: function(element, aFunction, parent = null) { - if (element instanceof Ci.nsIDOMXULMenuListElement) { - element = element.menupopup; - } - let children = element.children; - let numChildren = children.length; - - - // if there are no children in this select, we add a dummy row so that at least something appears - if (numChildren == 0) { - aFunction.call(this, {label: ""}, {isGroup: false}, parent); - } - - for (let i = 0; i < numChildren; i++) { - let child = children[i]; - let style = window.getComputedStyle(child, null); - if (style.display !== "none") { - if (child instanceof HTMLOptionElement || - child instanceof Ci.nsIDOMXULSelectControlItemElement) { - aFunction.call(this, child, {isGroup: false}, parent); - } else if (child instanceof HTMLOptGroupElement) { - aFunction.call(this, child, {isGroup: true}); - this.forVisibleOptions(child, aFunction, child); - } - } - } - }, - - fireOnChange: function(element) { - let event = element.ownerDocument.createEvent("Events"); - event.initEvent("change", true, true, element.defaultView, 0, - false, false, false, false, null); - setTimeout(function() { - element.dispatchEvent(event); - }, 0); - }, - - fireOnCommand: function(element) { - let event = element.ownerDocument.createEvent("XULCommandEvent"); - event.initCommandEvent("command", true, true, element.defaultView, 0, - false, false, false, false, null); - setTimeout(function() { - element.dispatchEvent(event); - }, 0); - }, - - _isDisabledElement : function(element) { - let currentElement = element; - while (currentElement) { - // Must test with === in case a form has a field named "disabled". See bug 1263589. - if (currentElement.disabled === true) { - return true; - } - currentElement = currentElement.parentElement; - } - return false; - } -}; diff --git a/mobile/android/chrome/content/WebcompatReporter.js b/mobile/android/chrome/content/WebcompatReporter.js deleted file mode 100644 index 66aefdda0..000000000 --- a/mobile/android/chrome/content/WebcompatReporter.js +++ /dev/null @@ -1,144 +0,0 @@ -/* 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/. */ - -var { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); - -var WebcompatReporter = { - menuItem: null, - menuItemEnabled: null, - init: function() { - Services.obs.addObserver(this, "DesktopMode:Change", false); - Services.obs.addObserver(this, "chrome-document-global-created", false); - Services.obs.addObserver(this, "content-document-global-created", false); - - let visible = true; - if ("@mozilla.org/parental-controls-service;1" in Cc) { - let pc = Cc["@mozilla.org/parental-controls-service;1"].createInstance(Ci.nsIParentalControlsService); - visible = !pc.parentalControlsEnabled; - } - - this.addMenuItem(visible); - }, - - observe: function(subject, topic, data) { - if (topic == "content-document-global-created" || topic == "chrome-document-global-created") { - let win = subject; - let currentURI = win.document.documentURI; - - // Ignore non top-level documents - if (currentURI !== win.top.location.href) { - return; - } - - if (!this.menuItemEnabled && this.isReportableUrl(currentURI)) { - NativeWindow.menu.update(this.menuItem, {enabled: true}); - this.menuItemEnabled = true; - } else if (this.menuItemEnabled && !this.isReportableUrl(currentURI)) { - NativeWindow.menu.update(this.menuItem, {enabled: false}); - this.menuItemEnabled = false; - } - } else if (topic === "DesktopMode:Change") { - let args = JSON.parse(data); - let tab = BrowserApp.getTabForId(args.tabId); - let currentURI = tab.browser.currentURI.spec; - if (args.desktopMode && this.isReportableUrl(currentURI)) { - this.reportDesktopModePrompt(tab); - } - } - }, - - addMenuItem: function(visible) { - this.menuItem = NativeWindow.menu.add({ - name: this.strings.GetStringFromName("webcompat.menu.name"), - callback: () => { - Promise.resolve(BrowserApp.selectedTab).then(this.getScreenshot) - .then(this.reportIssue) - .catch(Cu.reportError); - }, - enabled: false, - visible: visible, - }); - }, - - getScreenshot: (tab) => { - return new Promise((resolve) => { - try { - let win = tab.window; - let dpr = win.devicePixelRatio; - let canvas = win.document.createElement("canvas"); - let ctx = canvas.getContext("2d"); - // Grab the visible viewport coordinates - let x = win.document.documentElement.scrollLeft; - let y = win.document.documentElement.scrollTop; - let w = win.innerWidth; - let h = win.innerHeight; - // Scale according to devicePixelRatio and coordinates - canvas.width = dpr * w; - canvas.height = dpr * h; - ctx.scale(dpr, dpr); - ctx.drawWindow(win, x, y, w, h, '#ffffff'); - let screenshot = canvas.toDataURL(); - resolve({tab: tab, data: screenshot}); - } catch (e) { - // drawWindow can fail depending on memory or surface size. Rather than reject here, - // we resolve the URL so the user can continue to file an issue without a screenshot. - Cu.reportError("WebCompatReporter: getting a screenshot failed: " + e); - resolve({tab: tab}); - } - }); - }, - - isReportableUrl: function(url) { - return url && !(url.startsWith("about") || - url.startsWith("chrome") || - url.startsWith("file") || - url.startsWith("resource")); - }, - - reportDesktopModePrompt: function(tab) { - let message = this.strings.GetStringFromName("webcompat.reportDesktopMode.message"); - let options = { - action: { - label: this.strings.GetStringFromName("webcompat.reportDesktopModeYes.label"), - callback: () => this.reportIssue({tab: tab}) - } - }; - Snackbars.show(message, Snackbars.LENGTH_LONG, options); - }, - - reportIssue: (tabData) => { - return new Promise((resolve) => { - const WEBCOMPAT_ORIGIN = "https://webcompat.com"; - let url = tabData.tab.browser.currentURI.spec - let webcompatURL = `${WEBCOMPAT_ORIGIN}/issues/new?url=${url}`; - - if (tabData.data && typeof tabData.data === "string") { - BrowserApp.deck.addEventListener("DOMContentLoaded", function sendDataToTab(event) { - BrowserApp.deck.removeEventListener("DOMContentLoaded", sendDataToTab, false); - - if (event.target.defaultView.location.origin === WEBCOMPAT_ORIGIN) { - // Waive Xray vision so event.origin is not chrome://browser on the other side. - let win = Cu.waiveXrays(event.target.defaultView); - win.postMessage(tabData.data, WEBCOMPAT_ORIGIN); - } - }, false); - } - - let isPrivateTab = PrivateBrowsingUtils.isBrowserPrivate(tabData.tab.browser); - BrowserApp.addTab(webcompatURL, {parentId: tabData.tab.id, isPrivate: isPrivateTab}); - resolve(); - }); - } -}; - -XPCOMUtils.defineLazyGetter(WebcompatReporter, "strings", function() { - return Services.strings.createBundle("chrome://browser/locale/webcompatReporter.properties"); -}); diff --git a/mobile/android/chrome/content/WebrtcUI.js b/mobile/android/chrome/content/WebrtcUI.js deleted file mode 100644 index 475d05bd2..000000000 --- a/mobile/android/chrome/content/WebrtcUI.js +++ /dev/null @@ -1,302 +0,0 @@ -/* 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"; - -this.EXPORTED_SYMBOLS = ["WebrtcUI"]; - -XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls", "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService"); -XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm"); - -var WebrtcUI = { - _notificationId: null, - - // Add-ons can override stock permission behavior by doing: - // - // var stockObserve = WebrtcUI.observe; - // - // webrtcUI.observe = function(aSubject, aTopic, aData) { - // switch (aTopic) { - // case "PeerConnection:request": { - // // new code. - // break; - // ... - // default: - // return stockObserve.call(this, aSubject, aTopic, aData); - // - // See browser/modules/webrtcUI.jsm for details. - - observe: function(aSubject, aTopic, aData) { - if (aTopic === "getUserMedia:request") { - RuntimePermissions - .waitForPermissions(this._determineNeededRuntimePermissions(aSubject)) - .then((permissionGranted) => { - if (permissionGranted) { - WebrtcUI.handleGumRequest(aSubject, aTopic, aData); - } else { - Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID); - }}); - } else if (aTopic === "PeerConnection:request") { - this.handlePCRequest(aSubject, aTopic, aData); - } else if (aTopic === "recording-device-events") { - switch (aData) { - case "shutdown": - case "starting": - this.notify(); - break; - } - } else if (aTopic === "VideoCapture:Paused") { - if (this._notificationId) { - Notifications.cancel(this._notificationId); - this._notificationId = null; - } - } else if (aTopic === "VideoCapture:Resumed") { - this.notify(); - } - }, - - notify: function() { - let windows = MediaManagerService.activeMediaCaptureWindows; - let count = windows.length; - let msg = {}; - if (count == 0) { - if (this._notificationId) { - Notifications.cancel(this._notificationId); - this._notificationId = null; - } - } else { - let notificationOptions = { - title: Strings.brand.GetStringFromName("brandShortName"), - when: null, // hide the date row - light: [0xFF9500FF, 1000, 1000], - ongoing: true - }; - - let cameraActive = false; - let audioActive = false; - for (let i = 0; i < count; i++) { - let win = windows.queryElementAt(i, Ci.nsIDOMWindow); - let hasAudio = {}; - let hasVideo = {}; - MediaManagerService.mediaCaptureWindowState(win, hasVideo, hasAudio); - if (hasVideo.value) cameraActive = true; - if (hasAudio.value) audioActive = true; - } - - if (cameraActive && audioActive) { - notificationOptions.message = Strings.browser.GetStringFromName("getUserMedia.sharingCameraAndMicrophone.message2"); - notificationOptions.icon = "drawable:alert_mic_camera"; - } else if (cameraActive) { - notificationOptions.message = Strings.browser.GetStringFromName("getUserMedia.sharingCamera.message2"); - notificationOptions.icon = "drawable:alert_camera"; - } else if (audioActive) { - notificationOptions.message = Strings.browser.GetStringFromName("getUserMedia.sharingMicrophone.message2"); - notificationOptions.icon = "drawable:alert_mic"; - } else { - // somethings wrong. lets throw - throw "Couldn't find any cameras or microphones being used" - } - - if (this._notificationId) - Notifications.update(this._notificationId, notificationOptions); - else - this._notificationId = Notifications.create(notificationOptions); - if (count > 1) - msg.count = count; - } - }, - - handlePCRequest: function handlePCRequest(aSubject, aTopic, aData) { - aSubject = aSubject.wrappedJSObject; - let { callID } = aSubject; - // Also available: windowID, isSecure, innerWindowID. For contentWindow do: - // - // let contentWindow = Services.wm.getOuterWindowWithId(windowID); - - Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID); - }, - - handleGumRequest: function handleGumRequest(aSubject, aTopic, aData) { - let constraints = aSubject.getConstraints(); - let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID); - - contentWindow.navigator.mozGetUserMediaDevices( - constraints, - function (devices) { - if (!ParentalControls.isAllowed(ParentalControls.CAMERA_MICROPHONE)) { - Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID); - WebrtcUI.showBlockMessage(devices); - return; - } - - WebrtcUI.prompt(contentWindow, aSubject.callID, constraints.audio, - constraints.video, devices); - }, - function (error) { - Cu.reportError(error); - }, - aSubject.innerWindowID, - aSubject.callID); - }, - - getDeviceButtons: function(audioDevices, videoDevices, aCallID, aUri) { - return [{ - label: Strings.browser.GetStringFromName("getUserMedia.denyRequest.label"), - callback: function() { - Services.obs.notifyObservers(null, "getUserMedia:response:deny", aCallID); - } - }, - { - label: Strings.browser.GetStringFromName("getUserMedia.shareRequest.label"), - callback: function(checked /* ignored */, inputs) { - let allowedDevices = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); - - let audioId = 0; - if (inputs && inputs.audioDevice != undefined) - audioId = inputs.audioDevice; - if (audioDevices[audioId]) - allowedDevices.appendElement(audioDevices[audioId], /*weak =*/ false); - - let videoId = 0; - if (inputs && inputs.videoSource != undefined) - videoId = inputs.videoSource; - if (videoDevices[videoId]) { - allowedDevices.appendElement(videoDevices[videoId], /*weak =*/ false); - let perms = Services.perms; - // Although the lifetime is "session" it will be removed upon - // use so it's more of a one-shot. - perms.add(aUri, "MediaManagerVideo", perms.ALLOW_ACTION, perms.EXPIRE_SESSION); - } - - Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID); - }, - positive: true - }]; - }, - - _determineNeededRuntimePermissions: function(aSubject) { - let permissions = []; - - let constraints = aSubject.getConstraints(); - if (constraints.video) { - permissions.push(RuntimePermissions.CAMERA); - } - if (constraints.audio) { - permissions.push(RuntimePermissions.RECORD_AUDIO); - } - - return permissions; - }, - - // Get a list of string names for devices. Ensures that none of the strings are blank - _getList: function(aDevices, aType) { - let defaultCount = 0; - return aDevices.map(function(device) { - // if this is a Camera input, convert the name to something readable - let res = /Camera\ \d+,\ Facing (front|back)/.exec(device.name); - if (res) - return Strings.browser.GetStringFromName("getUserMedia." + aType + "." + res[1] + "Camera"); - - if (device.name.startsWith("&") && device.name.endsWith(";")) - return Strings.browser.GetStringFromName(device.name.substring(1, device.name.length -1)); - - if (device.name.trim() == "") { - defaultCount++; - return Strings.browser.formatStringFromName("getUserMedia." + aType + ".default", [defaultCount], 1); - } - return device.name - }, this); - }, - - _addDevicesToOptions: function(aDevices, aType, aOptions) { - if (aDevices.length) { - - // Filter out empty items from the list - let list = this._getList(aDevices, aType); - - if (list.length > 0) { - aOptions.inputs.push({ - id: aType, - type: "menulist", - label: Strings.browser.GetStringFromName("getUserMedia." + aType + ".prompt"), - values: list - }); - - } - } - }, - - showBlockMessage: function(aDevices) { - let microphone = false; - let camera = false; - - for (let device of aDevices) { - device = device.QueryInterface(Ci.nsIMediaDevice); - if (device.type == "audio") { - microphone = true; - } else if (device.type == "video") { - camera = true; - } - } - - let message; - if (microphone && !camera) { - message = Strings.browser.GetStringFromName("getUserMedia.blockedMicrophoneAccess"); - } else if (camera && !microphone) { - message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAccess"); - } else { - message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAndMicrophoneAccess"); - } - - NativeWindow.doorhanger.show(message, "webrtc-blocked", [], BrowserApp.selectedTab.id, {}); - }, - - prompt: function prompt(aContentWindow, aCallID, aAudioRequested, - aVideoRequested, aDevices) { - let audioDevices = []; - let videoDevices = []; - for (let device of aDevices) { - device = device.QueryInterface(Ci.nsIMediaDevice); - switch (device.type) { - case "audio": - if (aAudioRequested) - audioDevices.push(device); - break; - case "video": - if (aVideoRequested) - videoDevices.push(device); - break; - } - } - - let requestType; - if (audioDevices.length && videoDevices.length) - requestType = "CameraAndMicrophone"; - else if (audioDevices.length) - requestType = "Microphone"; - else if (videoDevices.length) - requestType = "Camera"; - else - return; - - let uri = aContentWindow.document.documentURIObject; - let host = uri.host; - let requestor = BrowserApp.manifest ? "'" + BrowserApp.manifest.name + "'" : host; - let message = Strings.browser.formatStringFromName("getUserMedia.share" + requestType + ".message", [ requestor ], 1); - - let options = { inputs: [] }; - if (videoDevices.length > 1 || audioDevices.length > 0) { - // videoSource is both the string used for l10n lookup and the object that will be returned - this._addDevicesToOptions(videoDevices, "videoSource", options); - } - - if (audioDevices.length > 1 || videoDevices.length > 0) { - this._addDevicesToOptions(audioDevices, "audioDevice", options); - } - - let buttons = this.getDeviceButtons(audioDevices, videoDevices, aCallID, uri); - - NativeWindow.doorhanger.show(message, "webrtc-request", buttons, BrowserApp.selectedTab.id, options, "WEBRTC"); - } -} diff --git a/mobile/android/chrome/content/about.js b/mobile/android/chrome/content/about.js deleted file mode 100644 index 8c9acdf8a..000000000 --- a/mobile/android/chrome/content/about.js +++ /dev/null @@ -1,151 +0,0 @@ -/* 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/. */ - -var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils, Cr = Components.results; -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -function init() { - // Include the build date and a warning about Telemetry - // if this is an "a#" (nightly or aurora) build -#expand const version = "__MOZ_APP_VERSION_DISPLAY__"; - if (/a\d+$/.test(version)) { - let buildID = Services.appinfo.appBuildID; - let buildDate = buildID.slice(0, 4) + "-" + buildID.slice(4, 6) + "-" + buildID.slice(6, 8); - let br = document.createElement("br"); - let versionPara = document.getElementById("version"); - versionPara.appendChild(br); - let date = document.createTextNode("(" + buildDate + ")"); - versionPara.appendChild(date); - document.getElementById("telemetry").hidden = false; - } - - // Include the Distribution information if available - try { - let distroId = Services.prefs.getCharPref("distribution.id"); - if (distroId) { - let distroVersion = Services.prefs.getCharPref("distribution.version"); - let distroIdField = document.getElementById("distributionID"); - distroIdField.textContent = distroId + " - " + distroVersion; - distroIdField.hidden = false; - - let distroAbout = Services.prefs.getComplexValue("distribution.about", Ci.nsISupportsString); - let distroField = document.getElementById("distributionAbout"); - distroField.textContent = distroAbout; - distroField.hidden = false; - } - } catch (e) { - // Pref is unset - } - - // get URLs from prefs - try { - let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); - - let links = [ - {id: "releaseNotesURL", pref: "app.releaseNotesURL"}, - {id: "supportURL", pref: "app.supportURL"}, - {id: "faqURL", pref: "app.faqURL"}, - {id: "privacyURL", pref: "app.privacyURL"}, - {id: "creditsURL", pref: "app.creditsURL"}, - ]; - - links.forEach(function(link) { - let url = formatter.formatURLPref(link.pref); - let element = document.getElementById(link.id); - element.setAttribute("href", url); - }); - } catch (ex) {} - -#ifdef MOZ_UPDATER - let Updater = { - update: null, - - init: function() { - Services.obs.addObserver(this, "Update:CheckResult", false); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "Update:CheckResult") { - showUpdateMessage(aData); - } - }, - }; - - Updater.init(); - - function checkForUpdates() { - showCheckingMessage(); - - Services.androidBridge.handleGeckoMessage({ type: "Update:Check" }); - } - - function downloadUpdate() { - Services.androidBridge.handleGeckoMessage({ type: "Update:Download" }); - } - - function installUpdate() { - showCheckAction(); - - Services.androidBridge.handleGeckoMessage({ type: "Update:Install" }); - } - - let updateLink = document.getElementById("updateLink"); - let checkingSpan = document.getElementById("update-message-checking"); - let noneSpan = document.getElementById("update-message-none"); - let foundSpan = document.getElementById("update-message-found"); - let downloadingSpan = document.getElementById("update-message-downloading"); - let downloadedSpan = document.getElementById("update-message-downloaded"); - - updateLink.onclick = checkForUpdates; - foundSpan.onclick = downloadUpdate; - downloadedSpan.onclick = installUpdate; - - function showCheckAction() { - checkingSpan.style.display = "none"; - noneSpan.style.display = "none"; - foundSpan.style.display = "none"; - downloadingSpan.style.display = "none"; - downloadedSpan.style.display = "none"; - updateLink.style.display = "block"; - } - - function showCheckingMessage() { - updateLink.style.display = "none"; - noneSpan.style.display = "none"; - foundSpan.style.display = "none"; - downloadingSpan.style.display = "none"; - downloadedSpan.style.display = "none"; - checkingSpan.style.display = "block"; - } - - function showUpdateMessage(aResult) { - updateLink.style.display = "none"; - checkingSpan.style.display = "none"; - noneSpan.style.display = "none"; - foundSpan.style.display = "none"; - downloadingSpan.style.display = "none"; - downloadedSpan.style.display = "none"; - - // the aResult values come from mobile/android/base/UpdateServiceHelper.java - switch (aResult) { - case "NOT_AVAILABLE": - noneSpan.style.display = "block"; - setTimeout(showCheckAction, 2000); - break; - case "AVAILABLE": - foundSpan.style.display = "block"; - break; - case "DOWNLOADING": - downloadingSpan.style.display = "block"; - break; - case "DOWNLOADED": - downloadedSpan.style.display = "block"; - break; - } - } -#endif -} - -document.addEventListener("DOMContentLoaded", init, false); diff --git a/mobile/android/chrome/content/about.xhtml b/mobile/android/chrome/content/about.xhtml deleted file mode 100644 index 8a4c28357..000000000 --- a/mobile/android/chrome/content/about.xhtml +++ /dev/null @@ -1,77 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html [ -<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > -%brandDTD; -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> -%globalDTD; -<!ENTITY % fennecDTD SYSTEM "chrome://browser/locale/about.dtd"> -%fennecDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> - <meta name="viewport" content="width=480; initial-scale=.6667; user-scalable=no"/> - <title>&aboutPage.title;</title> - <link rel="stylesheet" href="chrome://browser/skin/aboutPage.css" type="text/css"/> - <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" /> -</head> - -<body dir="&locale.dir;"> - <div id="header"> - <div id="wordmark"></div> -#expand <p id="version">__MOZ_APP_VERSION_DISPLAY__</p> - </div> - - <div id="banner"> - <div id="logo"/> -#ifdef MOZ_UPDATER - <div id="updateBox"> - <a id="updateLink" href="">&aboutPage.checkForUpdates.link;</a> - <span id="update-message-checking">&aboutPage.checkForUpdates.checking;</span> - <span id="update-message-none">&aboutPage.checkForUpdates.none;</span> - <span id="update-message-found">&aboutPage.checkForUpdates.available2;</span> - <span id="update-message-downloading">&aboutPage.checkForUpdates.downloading;</span> - <span id="update-message-downloaded">&aboutPage.checkForUpdates.downloaded2;</span> - </div> -#endif - - <div id="messages"> - <p id="distributionAbout" hidden="true"/> - <p id="distributionID" hidden="true"/> - <p id="telemetry" hidden="true"> - &aboutPage.warningVersion; -#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT - &aboutPage.telemetryStart;<a href="https://www.mozilla.org/">&aboutPage.telemetryMozillaLink;</a>&aboutPage.telemetryEnd; -#endif - </p> - </div> - - </div> - - <ul id="aboutLinks"> - <div class="top-border"></div> - <li><a id="faqURL">&aboutPage.faq.label;</a></li> - <li><a id="supportURL">&aboutPage.support.label;</a></li> - <li><a id="privacyURL">&aboutPage.privacyPolicy.label;</a></li> - <li><a href="about:rights">&aboutPage.rights.label;</a></li> - <li><a id="releaseNotesURL">&aboutPage.relNotes.label;</a></li> - <li><a id="creditsURL">&aboutPage.credits.label;</a></li> - <li><a href="about:license">&aboutPage.license.label;</a></li> - <div class="bottom-border"></div> - </ul> - -#ifdef RELEASE_OR_BETA - <div id="aboutDetails"> - <p>&aboutPage.logoTrademark;</p> - </div> -#endif - - <script type="application/javascript;version=1.8" src="chrome://browser/content/about.js" /> - -</body> -</html> diff --git a/mobile/android/chrome/content/aboutAddons.js b/mobile/android/chrome/content/aboutAddons.js deleted file mode 100644 index becf56a32..000000000 --- a/mobile/android/chrome/content/aboutAddons.js +++ /dev/null @@ -1,609 +0,0 @@ -/* 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"; - -/*globals gChromeWin */ - -var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm") -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -const AMO_ICON = "chrome://browser/skin/images/amo-logo.png"; - -var gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties"); - -XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow) - .QueryInterface(Ci.nsIDOMChromeWindow); -}); -XPCOMUtils.defineLazyModuleGetter(window, "Preferences", - "resource://gre/modules/Preferences.jsm"); - -var ContextMenus = { - target: null, - - init: function() { - document.addEventListener("contextmenu", this, false); - - document.getElementById("contextmenu-enable").addEventListener("click", ContextMenus.enable.bind(this), false); - document.getElementById("contextmenu-disable").addEventListener("click", ContextMenus.disable.bind(this), false); - document.getElementById("contextmenu-uninstall").addEventListener("click", ContextMenus.uninstall.bind(this), false); - - // XXX - Hack to fix bug 985867 for now - document.addEventListener("touchstart", function() { }); - }, - - handleEvent: function(event) { - // store the target of context menu events so that we know which app to act on - this.target = event.target; - while (!this.target.hasAttribute("contextmenu")) { - this.target = this.target.parentNode; - } - - if (!this.target) { - document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); - document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); - document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); - return; - } - - let addon = this.target.addon; - if (addon.scope == AddonManager.SCOPE_APPLICATION) { - document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); - } else { - document.getElementById("contextmenu-uninstall").removeAttribute("hidden"); - } - - // Hide the enable/disable context menu items if the add-on was disabled by - // Firefox (e.g. unsigned or blocklisted add-on). - if (addon.appDisabled) { - document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); - document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); - return; - } - - let enabled = this.target.getAttribute("isDisabled") != "true"; - if (enabled) { - document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); - document.getElementById("contextmenu-disable").removeAttribute("hidden"); - } else { - document.getElementById("contextmenu-enable").removeAttribute("hidden"); - document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); - } - }, - - enable: function(event) { - Addons.setEnabled(true, this.target.addon); - this.target = null; - }, - - disable: function (event) { - Addons.setEnabled(false, this.target.addon); - this.target = null; - }, - - uninstall: function (event) { - Addons.uninstall(this.target.addon); - this.target = null; - } -} - -function init() { - window.addEventListener("popstate", onPopState, false); - - AddonManager.addInstallListener(Addons); - AddonManager.addAddonListener(Addons); - Addons.init(); - showList(); - ContextMenus.init(); -} - - -function uninit() { - AddonManager.removeInstallListener(Addons); - AddonManager.removeAddonListener(Addons); -} - -function openLink(url) { - let BrowserApp = gChromeWin.BrowserApp; - BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }); -} - -function onPopState(aEvent) { - // Called when back/forward is used to change the state of the page - if (aEvent.state) { - // Show the detail page for an addon - Addons.showDetails(Addons._getElementForAddon(aEvent.state.id)); - } else { - // Clear any previous detail addon - let detailItem = document.querySelector("#addons-details > .addon-item"); - detailItem.addon = null; - - showList(); - } -} - -function showList() { - // Hide the detail page and show the list - let details = document.querySelector("#addons-details"); - details.style.display = "none"; - let list = document.querySelector("#addons-list"); - list.style.display = "block"; - document.documentElement.removeAttribute("details"); -} - -var Addons = { - _restartCount: 0, - - _createItem: function _createItem(aAddon) { - let outer = document.createElement("div"); - outer.setAttribute("addonID", aAddon.id); - outer.className = "addon-item list-item"; - outer.setAttribute("role", "button"); - outer.setAttribute("contextmenu", "addonmenu"); - outer.addEventListener("click", function() { - this.showDetails(outer); - history.pushState({ id: aAddon.id }, document.title); - }.bind(this), true); - - let img = document.createElement("img"); - img.className = "icon"; - img.setAttribute("src", aAddon.iconURL || AMO_ICON); - outer.appendChild(img); - - let inner = document.createElement("div"); - inner.className = "inner"; - - let details = document.createElement("div"); - details.className = "details"; - inner.appendChild(details); - - let titlePart = document.createElement("div"); - titlePart.textContent = aAddon.name; - titlePart.className = "title"; - details.appendChild(titlePart); - - let versionPart = document.createElement("div"); - versionPart.textContent = aAddon.version; - versionPart.className = "version"; - details.appendChild(versionPart); - - if ("description" in aAddon) { - let descPart = document.createElement("div"); - descPart.textContent = aAddon.description; - descPart.className = "description"; - inner.appendChild(descPart); - } - - outer.appendChild(inner); - return outer; - }, - - _createBrowseItem: function _createBrowseItem() { - let outer = document.createElement("div"); - outer.className = "addon-item list-item"; - outer.setAttribute("role", "button"); - outer.addEventListener("click", function(event) { - try { - let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); - openLink(formatter.formatURLPref("extensions.getAddons.browseAddons")); - } catch (e) { - Cu.reportError(e); - } - }, true); - - let img = document.createElement("img"); - img.className = "icon"; - img.setAttribute("src", AMO_ICON); - outer.appendChild(img); - - let inner = document.createElement("div"); - inner.className = "inner"; - - let title = document.createElement("div"); - title.id = "browse-title"; - title.className = "title"; - title.textContent = gStringBundle.GetStringFromName("addons.browseAll"); - inner.appendChild(title); - - outer.appendChild(inner); - return outer; - }, - - _createItemForAddon: function _createItemForAddon(aAddon) { - let appManaged = (aAddon.scope == AddonManager.SCOPE_APPLICATION); - let opType = this._getOpTypeForOperations(aAddon.pendingOperations); - let updateable = (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) > 0; - let uninstallable = (aAddon.permissions & AddonManager.PERM_CAN_UNINSTALL) > 0; - - // TODO(matt): Add support for OPTIONS_TYPE_INLINE_BROWSER once bug 1302504 lands. - let optionsURL; - switch (aAddon.optionsType) { - case AddonManager.OPTIONS_TYPE_INLINE: - optionsURL = aAddon.optionsURL || ""; - break; - default: - optionsURL = ""; - } - - let blocked = ""; - switch(aAddon.blocklistState) { - case Ci.nsIBlocklistService.STATE_BLOCKED: - blocked = "blocked"; - break; - case Ci.nsIBlocklistService.STATE_SOFTBLOCKED: - blocked = "softBlocked"; - break; - case Ci.nsIBlocklistService.STATE_OUTDATED: - blocked = "outdated"; - break; - } - - let item = this._createItem(aAddon); - item.setAttribute("isDisabled", !aAddon.isActive); - item.setAttribute("isUnsigned", aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING); - item.setAttribute("opType", opType); - item.setAttribute("updateable", updateable); - if (blocked) - item.setAttribute("blockedStatus", blocked); - item.setAttribute("optionsURL", optionsURL); - item.addon = aAddon; - - return item; - }, - - _getElementForAddon: function(aKey) { - let list = document.getElementById("addons-list"); - let element = list.querySelector("div[addonID=\"" + CSS.escape(aKey) + "\"]"); - return element; - }, - - init: function init() { - let self = this; - AddonManager.getAllAddons(function(aAddons) { - // Clear all content before filling the addons - let list = document.getElementById("addons-list"); - list.innerHTML = ""; - - aAddons.sort(function(a,b) { - return a.name.localeCompare(b.name); - }); - for (let i=0; i<aAddons.length; i++) { - // Don't create item for system add-ons. - if (aAddons[i].isSystem) - continue; - - let item = self._createItemForAddon(aAddons[i]); - list.appendChild(item); - } - - // Add a "Browse all Firefox Add-ons" item to the bottom of the list. - let browseItem = self._createBrowseItem(); - list.appendChild(browseItem); - }); - - document.getElementById("uninstall-btn").addEventListener("click", Addons.uninstallCurrent.bind(this), false); - document.getElementById("cancel-btn").addEventListener("click", Addons.cancelUninstall.bind(this), false); - document.getElementById("disable-btn").addEventListener("click", Addons.disable.bind(this), false); - document.getElementById("enable-btn").addEventListener("click", Addons.enable.bind(this), false); - - document.getElementById("unsigned-learn-more").addEventListener("click", function() { - openLink(Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"); - }, false); - }, - - _getOpTypeForOperations: function _getOpTypeForOperations(aOperations) { - if (aOperations & AddonManager.PENDING_UNINSTALL) - return "needs-uninstall"; - if (aOperations & AddonManager.PENDING_ENABLE) - return "needs-enable"; - if (aOperations & AddonManager.PENDING_DISABLE) - return "needs-disable"; - return ""; - }, - - showDetails: function showDetails(aListItem) { - // This function removes and returns the text content of aNode without - // removing any child elements. Removing the text nodes ensures any XBL - // bindings apply properly. - function stripTextNodes(aNode) { - var text = ""; - for (var i = 0; i < aNode.childNodes.length; i++) { - if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { - text += aNode.childNodes[i].textContent; - aNode.removeChild(aNode.childNodes[i--]); - } else { - text += stripTextNodes(aNode.childNodes[i]); - } - } - return text; - } - - let detailItem = document.querySelector("#addons-details > .addon-item"); - detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled")); - detailItem.setAttribute("isUnsigned", aListItem.getAttribute("isUnsigned")); - detailItem.setAttribute("opType", aListItem.getAttribute("opType")); - detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL")); - let addon = detailItem.addon = aListItem.addon; - - let favicon = document.querySelector("#addons-details > .addon-item .icon"); - favicon.setAttribute("src", addon.iconURL || AMO_ICON); - - detailItem.querySelector(".title").textContent = addon.name; - detailItem.querySelector(".version").textContent = addon.version; - detailItem.querySelector(".description-full").textContent = addon.description; - detailItem.querySelector(".status-uninstalled").textContent = - gStringBundle.formatStringFromName("addonStatus.uninstalled", [addon.name], 1); - - let enableBtn = document.getElementById("enable-btn"); - if (addon.appDisabled) { - enableBtn.setAttribute("disabled", "true"); - } else { - enableBtn.removeAttribute("disabled"); - } - - let uninstallBtn = document.getElementById("uninstall-btn"); - if (addon.scope == AddonManager.SCOPE_APPLICATION) { - uninstallBtn.setAttribute("disabled", "true"); - } else { - uninstallBtn.removeAttribute("disabled"); - } - - let box = document.querySelector("#addons-details > .addon-item .options-box"); - box.innerHTML = ""; - - // Retrieve the extensions preferences - try { - let optionsURL = aListItem.getAttribute("optionsURL"); - let xhr = new XMLHttpRequest(); - xhr.open("GET", optionsURL, true); - xhr.onload = function(e) { - if (xhr.responseXML) { - // Only allow <setting> for now - let settings = xhr.responseXML.querySelectorAll(":root > setting"); - if (settings.length > 0) { - for (let i = 0; i < settings.length; i++) { - var setting = settings[i]; - var desc = stripTextNodes(setting).trim(); - if (!setting.hasAttribute("desc")) { - setting.setAttribute("desc", desc); - } - box.appendChild(setting); - } - // Send an event so add-ons can prepopulate any non-preference based - // settings - let event = document.createEvent("Events"); - event.initEvent("AddonOptionsLoad", true, false); - window.dispatchEvent(event); - } else { - // Reset the options URL to hide the options header if there are no - // valid settings to show. - detailItem.setAttribute("optionsURL", ""); - } - - // Also send a notification to match the behavior of desktop Firefox - let id = aListItem.getAttribute("addonID"); - Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id); - } - } - xhr.send(null); - } catch (e) { } - - let list = document.querySelector("#addons-list"); - list.style.display = "none"; - let details = document.querySelector("#addons-details"); - details.style.display = "block"; - document.documentElement.setAttribute("details", "true"); - }, - - setEnabled: function setEnabled(aValue, aAddon) { - let detailItem = document.querySelector("#addons-details > .addon-item"); - let addon = aAddon || detailItem.addon; - if (!addon) - return; - - let listItem = this._getElementForAddon(addon.id); - - let opType; - if (addon.type == "theme") { - if (aValue) { - // We can have only one theme enabled, so disable the current one if any - let list = document.getElementById("addons-list"); - let item = list.firstElementChild; - while (item) { - if (item.addon && (item.addon.type == "theme") && (item.addon.isActive)) { - item.addon.userDisabled = true; - item.setAttribute("isDisabled", true); - break; - } - item = item.nextSibling; - } - } - addon.userDisabled = !aValue; - } else if (addon.type == "locale") { - addon.userDisabled = !aValue; - } else { - addon.userDisabled = !aValue; - opType = this._getOpTypeForOperations(addon.pendingOperations); - - if ((addon.pendingOperations & AddonManager.PENDING_ENABLE) || - (addon.pendingOperations & AddonManager.PENDING_DISABLE)) { - this.showRestart(); - } else if (listItem && /needs-(enable|disable)/.test(listItem.getAttribute("opType"))) { - this.hideRestart(); - } - } - - if (addon == detailItem.addon) { - detailItem.setAttribute("isDisabled", !aValue); - if (opType) - detailItem.setAttribute("opType", opType); - else - detailItem.removeAttribute("opType"); - } - - // Sync to the list item - if (listItem) { - listItem.setAttribute("isDisabled", !aValue); - if (opType) - listItem.setAttribute("opType", opType); - else - listItem.removeAttribute("opType"); - } - }, - - enable: function enable() { - this.setEnabled(true); - }, - - disable: function disable() { - this.setEnabled(false); - }, - - uninstallCurrent: function uninstallCurrent() { - let detailItem = document.querySelector("#addons-details > .addon-item"); - - let addon = detailItem.addon; - if (!addon) - return; - - this.uninstall(addon); - }, - - uninstall: function uninstall(aAddon) { - if (!aAddon) { - return; - } - - let listItem = this._getElementForAddon(aAddon.id); - aAddon.uninstall(); - - if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) { - this.showRestart(); - - // A disabled addon doesn't need a restart so it has no pending ops and - // can't be cancelled - let opType = this._getOpTypeForOperations(aAddon.pendingOperations); - if (!aAddon.isActive && opType == "") - opType = "needs-uninstall"; - - detailItem.setAttribute("opType", opType); - listItem.setAttribute("opType", opType); - } - }, - - cancelUninstall: function ev_cancelUninstall() { - let detailItem = document.querySelector("#addons-details > .addon-item"); - let addon = detailItem.addon; - if (!addon) - return; - - addon.cancelUninstall(); - this.hideRestart(); - - let opType = this._getOpTypeForOperations(addon.pendingOperations); - detailItem.setAttribute("opType", opType); - - let listItem = this._getElementForAddon(addon.id); - listItem.setAttribute("opType", opType); - }, - - showRestart: function showRestart() { - this._restartCount++; - gChromeWin.XPInstallObserver.showRestartPrompt(); - }, - - hideRestart: function hideRestart() { - this._restartCount--; - if (this._restartCount == 0) - gChromeWin.XPInstallObserver.hideRestartPrompt(); - }, - - onEnabled: function(aAddon) { - let listItem = this._getElementForAddon(aAddon.id); - if (!listItem) - return; - - // Reload the details to pick up any options now that it's enabled. - listItem.setAttribute("optionsURL", aAddon.optionsURL || ""); - let detailItem = document.querySelector("#addons-details > .addon-item"); - if (aAddon == detailItem.addon) - this.showDetails(listItem); - }, - - onInstallEnded: function(aInstall, aAddon) { - let needsRestart = false; - if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE)) - needsRestart = true; - else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) - needsRestart = true; - - let list = document.getElementById("addons-list"); - let element = this._getElementForAddon(aAddon.id); - if (!element) { - element = this._createItemForAddon(aAddon); - list.insertBefore(element, list.firstElementChild); - } - - if (needsRestart) - element.setAttribute("opType", "needs-restart"); - }, - - onInstalled: function(aAddon) { - let list = document.getElementById("addons-list"); - let element = this._getElementForAddon(aAddon.id); - if (!element) { - element = this._createItemForAddon(aAddon); - - // Themes aren't considered active on install, so set existing as disabled, and new one enabled. - if (aAddon.type == "theme") { - let item = list.firstElementChild; - while (item) { - if (item.addon && (item.addon.type == "theme")) { - item.setAttribute("isDisabled", true); - } - item = item.nextSibling; - } - element.setAttribute("isDisabled", false); - } - - list.insertBefore(element, list.firstElementChild); - } - }, - - onUninstalled: function(aAddon) { - let list = document.getElementById("addons-list"); - let element = this._getElementForAddon(aAddon.id); - list.removeChild(element); - - // Go back if we're in the detail view of the add-on that was uninstalled. - let detailItem = document.querySelector("#addons-details > .addon-item"); - if (detailItem.addon.id == aAddon.id) { - history.back(); - } - }, - - onInstallFailed: function(aInstall) { - }, - - onDownloadProgress: function xpidm_onDownloadProgress(aInstall) { - }, - - onDownloadFailed: function(aInstall) { - }, - - onDownloadCancelled: function(aInstall) { - } -} - -window.addEventListener("load", init, false); -window.addEventListener("unload", uninit, false); diff --git a/mobile/android/chrome/content/aboutAddons.xhtml b/mobile/android/chrome/content/aboutAddons.xhtml deleted file mode 100644 index 42d9cfa9c..000000000 --- a/mobile/android/chrome/content/aboutAddons.xhtml +++ /dev/null @@ -1,63 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ -<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > -%brandDTD; -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" > -%globalDTD; -<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAddons.dtd" > -%aboutDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> - <title>&aboutAddons.title2;</title> - <meta name="viewport" content="width=device-width; user-scalable=0" /> - <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" /> - <link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/> - <link rel="stylesheet" href="chrome://browser/skin/aboutAddons.css" type="text/css"/> -</head> - -<body dir="&locale.dir;"> - <menu type="context" id="addonmenu"> - <menuitem id="contextmenu-enable" label="&addonAction.enable;"></menuitem> - <menuitem id="contextmenu-disable" label="&addonAction.disable;" ></menuitem> - <menuitem id="contextmenu-uninstall" label="&addonAction.uninstall;" ></menuitem> - </menu> - - <div id="addons-header" class="header"> - <div>&aboutAddons.header2;</div> - </div> - <div id="addons-list" class="list"> - </div> - - <div id="addons-details" class="list"> - <div class="addon-item list-item"> - <img class="icon"/> - <div class="inner"> - <div class="details"> - <div class="title"></div><div class="version"></div> - </div> - <div class="description-full"></div> - <div class="options-header">&aboutAddons.options;</div> - <div class="options-box"></div> - </div> - <div class="warn-unsigned">&addonUnsigned.message; <a id="unsigned-learn-more">&addonUnsigned.learnMore;</a></div> - <div class="status status-uninstalled show-on-uninstall"></div> - <div class="buttons"> - <button id="enable-btn" class="show-on-disable hide-on-enable hide-on-uninstall" >&addonAction.enable;</button> - <button id="disable-btn" class="show-on-enable hide-on-disable hide-on-uninstall" >&addonAction.disable;</button> - <button id="uninstall-btn" class="hide-on-uninstall" >&addonAction.uninstall;</button> - <button id="cancel-btn" class="show-on-uninstall" >&addonAction.undo;</button> - </div> - </div> - </div> - - <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutAddons.js"></script> -</body> -</html> diff --git a/mobile/android/chrome/content/aboutCertError.xhtml b/mobile/android/chrome/content/aboutCertError.xhtml deleted file mode 100644 index c5922e2fe..000000000 --- a/mobile/android/chrome/content/aboutCertError.xhtml +++ /dev/null @@ -1,264 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html [ - <!ENTITY % htmlDTD - PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "DTD/xhtml1-strict.dtd"> - %htmlDTD; - <!ENTITY % globalDTD - SYSTEM "chrome://global/locale/global.dtd"> - %globalDTD; - <!ENTITY % certerrorDTD - SYSTEM "chrome://browser/locale/aboutCertError.dtd"> - %certerrorDTD; -]> - -<!-- 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/. --> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <title>&certerror.pagetitle;</title> - <meta name="viewport" content="width=device-width; user-scalable=false" /> - <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" /> - <!-- This page currently uses the same favicon as neterror.xhtml. - If the location of the favicon is changed for both pages, the - FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h - should be updated. If this page starts using a different favicon - than neterrorm nsFaviconService->SetAndLoadFaviconForPage - should be updated to ignore this one as well. --> - <link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/certerror-warning.png"/> - - <script type="application/javascript"><![CDATA[ - // Error url MUST be formatted like this: - // about:certerror?e=error&u=url&d=desc - - // Note that this file uses document.documentURI to get - // the URL (with the format from above). This is because - // document.location.href gets the current URI off the docshell, - // which is the URL displayed in the location bar, i.e. - // the URI that the user attempted to load. - - function getCSSClass() - { - var url = document.documentURI; - var matches = url.match(/s\=([^&]+)\&/); - // s is optional, if no match just return nothing - if (!matches || matches.length < 2) - return ""; - - // parenthetical match is the second entry - return decodeURIComponent(matches[1]); - } - - function getDescription() - { - var url = document.documentURI; - var desc = url.search(/d\=/); - - // desc == -1 if not found; if so, return an empty string - // instead of what would turn out to be portions of the URI - if (desc == -1) - return ""; - - return decodeURIComponent(url.slice(desc + 2)); - } - - function initPage() - { - // Replace the "#1" string in the intro with the hostname. Trickier - // than it might seem since we want to preserve the <b> tags, but - // not allow for any injection by just using innerHTML. Instead, - // just find the right target text node. - var intro = document.getElementById('introContentP1'); - function replaceWithHost(node) { - if (node.textContent == "#1") - node.textContent = location.host; - else - for(var i = 0; i < node.childNodes.length; i++) - replaceWithHost(node.childNodes[i]); - }; - replaceWithHost(intro); - - if (getCSSClass() == "expertBadCert") { - toggle('technicalContent'); - toggle('expertContent'); - } - - // Disallow overrides if this is a Strict-Transport-Security - // host and the cert is bad (STS Spec section 7.3) or if the - // certerror is in a frame (bug 633691). - if (getCSSClass() == "badStsCert" || window != top) - document.getElementById("expertContent").setAttribute("hidden", "true"); - - var tech = document.getElementById("technicalContentText"); - if (tech) - tech.textContent = getDescription(); - - addDomainErrorLinks(); - } - - /* Try to preserve the links contained in the error description, like - the error code. - - Also, in the case of SSL error pages about domain mismatch, see if - we can hyperlink the user to the correct site. We don't want - to do this generically since it allows MitM attacks to redirect - users to a site under attacker control, but in certain cases - it is safe (and helpful!) to do so. Bug 402210 - */ - function addDomainErrorLinks() { - // Rather than textContent, we need to treat description as HTML - var sd = document.getElementById("technicalContentText"); - if (sd) { - var desc = getDescription(); - - // sanitize description text - see bug 441169 - - // First, find the index of the <a> tags we care about, being - // careful not to use an over-greedy regex. - var codeRe = /<a id="errorCode" title="([^"]+)">/; - var codeResult = codeRe.exec(desc); - var domainRe = /<a id="cert_domain_link" title="([^"]+)">/; - var domainResult = domainRe.exec(desc); - - // The order of these links in the description is fixed in - // TransportSecurityInfo.cpp:formatOverridableCertErrorMessage. - var firstResult = domainResult; - if (!domainResult) - firstResult = codeResult; - if (!firstResult) - return; - - // Remove sd's existing children - sd.textContent = ""; - - // Everything up to the first link should be text content. - sd.appendChild(document.createTextNode(desc.slice(0, firstResult.index))); - - // Now create the actual links. - if (domainResult) { - createLink(sd, "cert_domain_link", domainResult[1]) - // Append text for anything between the two links. - sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length, codeResult.index))); - } - createLink(sd, "errorCode", codeResult[1]) - - // Finally, append text for anything after the last closing </a>. - sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length))); - } - - // Then initialize the cert domain link. - var link = document.getElementById('cert_domain_link'); - if (!link) - return; - - var okHost = link.getAttribute("title"); - var thisHost = document.location.hostname; - var proto = document.location.protocol; - - // If okHost is a wildcard domain ("*.example.com") let's - // use "www" instead. "*.example.com" isn't going to - // get anyone anywhere useful. bug 432491 - okHost = okHost.replace(/^\*\./, "www."); - - /* case #1: - * example.com uses an invalid security certificate. - * - * The certificate is only valid for www.example.com - * - * Make sure to include the "." ahead of thisHost so that - * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com" - * - * We'd normally just use a RegExp here except that we lack a - * library function to escape them properly (bug 248062), and - * domain names are famous for having '.' characters in them, - * which would allow spurious and possibly hostile matches. - */ - if (okHost.endsWith("." + thisHost)) - link.href = proto + okHost; - - /* case #2: - * browser.garage.maemo.org uses an invalid security certificate. - * - * The certificate is only valid for garage.maemo.org - */ - if (thisHost.endsWith("." + okHost)) - link.href = proto + okHost; - - // If we set a link, meaning there's something helpful for - // the user here, expand the section by default - if (link.href && getCSSClass() != "expertBadCert") - toggle("technicalContent"); - } - - function createLink(el, id, text) { - var anchorEl = document.createElement("a"); - anchorEl.setAttribute("id", id); - anchorEl.setAttribute("title", text); - anchorEl.appendChild(document.createTextNode(text)); - el.appendChild(anchorEl); - } - - function toggle(id) { - var el = document.getElementById(id); - if (el.hasAttribute("collapsed")) - el.removeAttribute("collapsed"); - else - el.setAttribute("collapsed", true); - } - ]]></script> - </head> - - <body id="errorPage" class="certerror" dir="&locale.dir;"> - - <!-- PAGE CONTAINER (for styling purposes only) --> - <div id="errorPageContainer"> - - <!-- Error Title --> - <div id="errorTitle"> - <h1 class="errorTitleText">&certerror.longpagetitle;</h1> - </div> - - <!-- LONG CONTENT (the section most likely to require scrolling) --> - <div id="errorLongContent"> - <div id="introContent"> - <p id="introContentP1">&certerror.introPara1;</p> - </div> - - <div id="whatShouldIDoContent"> - <h2>&certerror.whatShouldIDo.heading;</h2> - <div id="whatShouldIDoContentText"> - <p>&certerror.whatShouldIDo.content;</p> - <button id="getMeOutOfHereButton">&certerror.getMeOutOfHere.label;</button> - </div> - </div> - - <!-- The following sections can be unhidden by default by setting the - "browser.xul.error_pages.expert_bad_cert" pref to true --> - <div id="technicalContent" collapsed="true"> - <h2 class="expander" onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2> - <p id="technicalContentText"/> - </div> - - <div id="expertContent" collapsed="true"> - <h2 class="expander" onclick="toggle('expertContent');" id="expertContentHeading">&certerror.expert.heading;</h2> - <div> - <p>&certerror.expert.content;</p> - <p>&certerror.expert.contentPara2;</p> - <button id="temporaryExceptionButton">&certerror.addTemporaryException.label;</button> - <button id="permanentExceptionButton">&certerror.addPermanentException.label;</button> - </div> - </div> - </div> - </div> - - <!-- - - Note: It is important to run the script this way, instead of using - - an onload handler. This is because error pages are loaded as - - LOAD_BACKGROUND, which means that onload handlers will not be executed. - --> - <script type="application/javascript">initPage();</script> - - </body> -</html> diff --git a/mobile/android/chrome/content/aboutDownloads.js b/mobile/android/chrome/content/aboutDownloads.js deleted file mode 100644 index add0a48e6..000000000 --- a/mobile/android/chrome/content/aboutDownloads.js +++ /dev/null @@ -1,373 +0,0 @@ -/* 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"; - -var Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); - -XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); - -var gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties"); -XPCOMUtils.defineLazyGetter(this, "strings", - () => Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties")); - -function deleteDownload(download) { - download.finalize(true).then(null, Cu.reportError); - OS.File.remove(download.target.path).then(null, ex => { - if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) { - Cu.reportError(ex); - } - }); -} - -var contextMenu = { - _items: [], - _targetDownload: null, - - init: function () { - let element = document.getElementById("downloadmenu"); - element.addEventListener("click", - event => event.download = this._targetDownload, - true); - this._items = [ - new ContextMenuItem("open", - download => download.succeeded, - download => download.launch().then(null, Cu.reportError)), - new ContextMenuItem("retry", - download => download.error || - (download.canceled && !download.hasPartialData), - download => download.start().then(null, Cu.reportError)), - new ContextMenuItem("remove", - download => download.stopped, - download => { - Downloads.getList(Downloads.ALL) - .then(list => list.remove(download)) - .then(null, Cu.reportError); - deleteDownload(download); - }), - new ContextMenuItem("pause", - download => !download.stopped && download.hasPartialData, - download => download.cancel().then(null, Cu.reportError)), - new ContextMenuItem("resume", - download => download.canceled && download.hasPartialData, - download => download.start().then(null, Cu.reportError)), - new ContextMenuItem("cancel", - download => !download.stopped || - (download.canceled && download.hasPartialData), - download => { - download.cancel().then(null, Cu.reportError); - download.removePartialData().then(null, Cu.reportError); - }), - // following menu item is a global action - new ContextMenuItem("removeall", - () => downloadLists.finished.length > 0, - () => downloadLists.removeFinished()) - ]; - }, - - addContextMenuEventListener: function (element) { - element.addEventListener("contextmenu", this.onContextMenu.bind(this)); - }, - - onContextMenu: function (event) { - let target = event.target; - while (target && !target.download) { - target = target.parentNode; - } - if (!target) { - Cu.reportError("No download found for context menu target"); - event.preventDefault(); - return; - } - - // capture the target download for menu items to use in a click event - this._targetDownload = target.download; - for (let item of this._items) { - item.updateVisibility(target.download); - } - } -}; - -function ContextMenuItem(name, isVisible, action) { - this.element = document.getElementById("contextmenu-" + name); - this.isVisible = isVisible; - - this.element.addEventListener("click", event => action(event.download)); -} - -ContextMenuItem.prototype = { - updateVisibility: function (download) { - this.element.hidden = !this.isVisible(download); - } -}; - -function DownloadListView(type, listElementId) { - this.listElement = document.getElementById(listElementId); - contextMenu.addContextMenuEventListener(this.listElement); - - this.items = new Map(); - - Downloads.getList(type) - .then(list => list.addView(this)) - .then(null, Cu.reportError); - - window.addEventListener("unload", event => { - Downloads.getList(type) - .then(list => list.removeView(this)) - .then(null, Cu.reportError); - }); -} - -DownloadListView.prototype = { - get finished() { - let finished = []; - for (let download of this.items.keys()) { - if (download.stopped && (!download.hasPartialData || download.error)) { - finished.push(download); - } - } - - return finished; - }, - - insertOrMoveItem: function (item) { - var compare = (a, b) => { - // active downloads always before stopped downloads - if (a.stopped != b.stopped) { - return b.stopped ? -1 : 1 - } - // most recent downloads first - return b.startTime - a.startTime; - }; - - let insertLocation = this.listElement.firstChild; - while (insertLocation && compare(item.download, insertLocation.download) > 0) { - insertLocation = insertLocation.nextElementSibling; - } - this.listElement.insertBefore(item.element, insertLocation); - }, - - onDownloadAdded: function (download) { - let item = new DownloadItem(download); - this.items.set(download, item); - this.insertOrMoveItem(item); - }, - - onDownloadChanged: function (download) { - let item = this.items.get(download); - if (!item) { - Cu.reportError("No DownloadItem found for download"); - return; - } - - if (item.stateChanged) { - this.insertOrMoveItem(item); - } - - item.onDownloadChanged(); - }, - - onDownloadRemoved: function (download) { - let item = this.items.get(download); - if (!item) { - Cu.reportError("No DownloadItem found for download"); - return; - } - - this.items.delete(download); - this.listElement.removeChild(item.element); - - Messaging.sendRequest({ - type: "Download:Remove", - path: download.target.path - }); - } -}; - -var downloadLists = { - init: function () { - this.publicDownloads = new DownloadListView(Downloads.PUBLIC, "public-downloads-list"); - this.privateDownloads = new DownloadListView(Downloads.PRIVATE, "private-downloads-list"); - }, - - get finished() { - return this.publicDownloads.finished.concat(this.privateDownloads.finished); - }, - - removeFinished: function () { - let finished = this.finished; - if (finished.length == 0) { - return; - } - - let title = strings.GetStringFromName("downloadAction.deleteAll"); - let messageForm = strings.GetStringFromName("downloadMessage.deleteAll"); - let message = PluralForm.get(finished.length, messageForm).replace("#1", finished.length); - - if (Services.prompt.confirm(null, title, message)) { - Downloads.getList(Downloads.ALL) - .then(list => { - for (let download of finished) { - list.remove(download).then(null, Cu.reportError); - deleteDownload(download); - } - }, Cu.reportError); - } - } -}; - -function DownloadItem(download) { - this._download = download; - this._updateFromDownload(); - - this._domain = DownloadUtils.getURIHost(download.source.url)[0]; - this._fileName = this._htmlEscape(OS.Path.basename(download.target.path)); - this._iconUrl = "moz-icon://" + this._fileName + "?size=64"; - this._startDate = this._htmlEscape(DownloadUtils.getReadableDates(download.startTime)[0]); - - this._element = this.createElement(); -} - -const kDownloadStatePropertyNames = [ - "stopped", - "succeeded", - "canceled", - "error", - "startTime" -]; - -DownloadItem.prototype = { - _htmlEscape : function (s) { - s = s.replace(/&/g, "&"); - s = s.replace(/>/g, ">"); - s = s.replace(/</g, "<"); - s = s.replace(/"/g, """); - s = s.replace(/'/g, "'"); - return s; - }, - - _updateFromDownload: function () { - this._state = {}; - kDownloadStatePropertyNames.forEach( - name => this._state[name] = this._download[name], - this); - }, - - get stateChanged() { - return kDownloadStatePropertyNames.some( - name => this._state[name] != this._download[name], - this); - }, - - get download() { - return this._download; - }, - get element() { - return this._element; - }, - - createElement: function() { - let template = document.getElementById("download-item"); - // TODO: use this once <template> is working - // let element = document.importNode(template.content, true); - - // simulate a <template> node... - let element = template.cloneNode(true); - element.removeAttribute("id"); - element.removeAttribute("style"); - - // launch the download if clicked - element.addEventListener("click", this.onClick.bind(this)); - - // set download as an expando property for the context menu - element.download = this.download; - - // fill in template placeholders - this.updateElement(element); - - return element; - }, - - updateElement: function (element) { - element.querySelector(".date").textContent = this.startDate; - element.querySelector(".domain").textContent = this.domain; - element.querySelector(".icon").src = this.iconUrl; - element.querySelector(".size").textContent = this.size; - element.querySelector(".state").textContent = this.stateDescription; - element.querySelector(".title").setAttribute("value", this.fileName); - }, - - onClick: function (event) { - if (this.download.succeeded) { - this.download.launch().then(null, Cu.reportError); - } - }, - - onDownloadChanged: function () { - this._updateFromDownload(); - this.updateElement(this.element); - }, - - // template properties below - get domain() { - return this._domain; - }, - get fileName() { - return this._fileName; - }, - get id() { - return this._id; - }, - get iconUrl() { - return this._iconUrl; - }, - - get size() { - if (this.download.succeeded && this.download.target.exists) { - return DownloadUtils.convertByteUnits(this.download.target.size).join(""); - } else if (this.download.hasProgress) { - return DownloadUtils.convertByteUnits(this.download.totalBytes).join(""); - } - return strings.GetStringFromName("downloadState.unknownSize"); - }, - - get startDate() { - return this._startDate; - }, - - get stateDescription() { - let name; - if (this.download.error) { - name = "downloadState.failed"; - } else if (this.download.canceled) { - if (this.download.hasPartialData) { - name = "downloadState.paused"; - } else { - name = "downloadState.canceled"; - } - } else if (!this.download.stopped) { - if (this.download.currentBytes > 0) { - name = "downloadState.downloading"; - } else { - name = "downloadState.starting"; - } - } - - if (name) { - return strings.GetStringFromName(name); - } - return ""; - } -}; - -window.addEventListener("DOMContentLoaded", event => { - contextMenu.init(); - downloadLists.init() -});
\ No newline at end of file diff --git a/mobile/android/chrome/content/aboutDownloads.xhtml b/mobile/android/chrome/content/aboutDownloads.xhtml deleted file mode 100644 index 6b9025694..000000000 --- a/mobile/android/chrome/content/aboutDownloads.xhtml +++ /dev/null @@ -1,62 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ -<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > -%brandDTD; -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" > -%globalDTD; -<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/aboutDownloads.dtd" > -%downloadsDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> -<head> - <title>&aboutDownloads.title;</title> - <meta name="viewport" content="width=device-width; user-scalable=0" /> - <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" /> - <link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/> - <link rel="stylesheet" href="chrome://browser/skin/aboutDownloads.css" type="text/css"/> -</head> - -<body dir="&locale.dir;"> - <menu type="context" id="downloadmenu"> - <menuitem id="contextmenu-open" label="&aboutDownloads.open;"></menuitem> - <menuitem id="contextmenu-retry" label="&aboutDownloads.retry;"></menuitem> - <menuitem id="contextmenu-remove" label="&aboutDownloads.remove;"></menuitem> - <menuitem id="contextmenu-pause" label="&aboutDownloads.pause;"></menuitem> - <menuitem id="contextmenu-resume" label="&aboutDownloads.resume;"></menuitem> - <menuitem id="contextmenu-cancel" label="&aboutDownloads.cancel;"></menuitem> - <menuitem id="contextmenu-removeall" label="&aboutDownloads.removeAll;"></menuitem> - </menu> - - <!--template id="download-item"--> - <li id="download-item" class="list-item" role="button" contextmenu="downloadmenu" style="display: none"> - <img class="icon" src=""/> - <div class="details"> - <div class="row"> - <!-- This is a hack so that we can crop this label in its center --> - <xul:label class="title" crop="center" value=""/> - <div class="date"></div> - </div> - <div class="size"></div> - <div class="domain"></div> - <div class="state"></div> - </div> - </li> - <!--/template--> - - <div class="header"> - <div>&aboutDownloads.header;</div> - </div> - <ul id="private-downloads-list" class="list"></ul> - <ul id="public-downloads-list" class="list"></ul> - <span id="no-downloads-indicator">&aboutDownloads.empty;</span> - <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutDownloads.js"/> -</body> -</html> diff --git a/mobile/android/chrome/content/aboutHealthReport.js b/mobile/android/chrome/content/aboutHealthReport.js deleted file mode 100644 index 070eb821d..000000000 --- a/mobile/android/chrome/content/aboutHealthReport.js +++ /dev/null @@ -1,192 +0,0 @@ -// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- -/* 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"; - -var { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Messaging.jsm"); -Cu.import("resource://gre/modules/SharedPreferences.jsm"); - -// Name of Android SharedPreference controlling whether to upload -// health reports. -const PREF_UPLOAD_ENABLED = "android.not_a_preference.healthreport.uploadEnabled"; - -// Name of Gecko Pref specifying report content location. -const PREF_REPORTURL = "datareporting.healthreport.about.reportUrl"; - -// Monotonically increasing wrapper API version number. -const WRAPPER_VERSION = 1; - -const EVENT_HEALTH_REQUEST = "HealthReport:Request"; -const EVENT_HEALTH_RESPONSE = "HealthReport:Response"; - -// about:healthreport prefs are stored in Firefox's default Android -// SharedPreferences. -var sharedPrefs = SharedPreferences.forApp(); - -var healthReportWrapper = { - init: function () { - let iframe = document.getElementById("remote-report"); - iframe.addEventListener("load", healthReportWrapper.initRemotePage, false); - let report = this._getReportURI(); - iframe.src = report.spec; - console.log("AboutHealthReport: loading content from " + report.spec); - - sharedPrefs.addObserver(PREF_UPLOAD_ENABLED, this, false); - Services.obs.addObserver(this, EVENT_HEALTH_RESPONSE, false); - }, - - observe: function (subject, topic, data) { - if (topic == PREF_UPLOAD_ENABLED) { - this.updatePrefState(); - } else if (topic == EVENT_HEALTH_RESPONSE) { - this.updatePayload(data); - } - }, - - uninit: function () { - sharedPrefs.removeObserver(PREF_UPLOAD_ENABLED, this); - Services.obs.removeObserver(this, EVENT_HEALTH_RESPONSE); - }, - - _getReportURI: function () { - let url = Services.urlFormatter.formatURLPref(PREF_REPORTURL); - // This handles URLs that already have query parameters. - let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL); - uri.query += ((uri.query != "") ? "&v=" : "v=") + WRAPPER_VERSION; - return uri; - }, - - onOptIn: function () { - console.log("AboutHealthReport: page sent opt-in command."); - sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, true); - this.updatePrefState(); - }, - - onOptOut: function () { - console.log("AboutHealthReport: page sent opt-out command."); - sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, false); - this.updatePrefState(); - }, - - updatePrefState: function () { - console.log("AboutHealthReport: sending pref state to page."); - try { - let prefs = { - enabled: sharedPrefs.getBoolPref(PREF_UPLOAD_ENABLED), - }; - this.injectData("prefs", prefs); - } catch (e) { - this.reportFailure(this.ERROR_PREFS_FAILED); - } - }, - - refreshPayload: function () { - console.log("AboutHealthReport: page requested fresh payload."); - Messaging.sendRequest({ - type: EVENT_HEALTH_REQUEST, - }); - }, - - updatePayload: function (data) { - healthReportWrapper.injectData("payload", data); - // Data is supposed to be a string, so the length should be - // defined. Just in case, we do this after injecting the data. - console.log("AboutHealthReport: sending payload to page " + - "(" + typeof(data) + " of length " + data.length + ")."); - }, - - injectData: function (type, content) { - let report = this._getReportURI(); - - // file: URIs can't be used for targetOrigin, so we use "*" for - // this special case. In all other cases, pass in the URL to the - // report so we properly restrict the message dispatch. - let reportUrl = (report.scheme == "file") ? "*" : report.spec; - - let data = { - type: type, - content: content, - }; - - let iframe = document.getElementById("remote-report"); - iframe.contentWindow.postMessage(data, reportUrl); - }, - - showSettings: function () { - console.log("AboutHealthReport: showing settings."); - Messaging.sendRequest({ - type: "Settings:Show", - resource: "preferences_vendor", - }); - }, - - launchUpdater: function () { - console.log("AboutHealthReport: launching updater."); - Messaging.sendRequest({ - type: "Updater:Launch", - }); - }, - - handleRemoteCommand: function (evt) { - switch (evt.detail.command) { - case "DisableDataSubmission": - this.onOptOut(); - break; - case "EnableDataSubmission": - this.onOptIn(); - break; - case "RequestCurrentPrefs": - this.updatePrefState(); - break; - case "RequestCurrentPayload": - this.refreshPayload(); - break; - case "ShowSettings": - this.showSettings(); - break; - case "LaunchUpdater": - this.launchUpdater(); - break; - default: - Cu.reportError("Unexpected remote command received: " + evt.detail.command + - ". Ignoring command."); - break; - } - }, - - initRemotePage: function () { - let iframe = document.getElementById("remote-report").contentDocument; - iframe.addEventListener("RemoteHealthReportCommand", - function onCommand(e) {healthReportWrapper.handleRemoteCommand(e);}, - false); - healthReportWrapper.injectData("begin", null); - }, - - // error handling - ERROR_INIT_FAILED: 1, - ERROR_PAYLOAD_FAILED: 2, - ERROR_PREFS_FAILED: 3, - - reportFailure: function (error) { - let details = { - errorType: error, - }; - healthReportWrapper.injectData("error", details); - }, - - handleInitFailure: function () { - healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED); - }, - - handlePayloadFailure: function () { - healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED); - }, -}; - -window.addEventListener("load", healthReportWrapper.init.bind(healthReportWrapper), false); -window.addEventListener("unload", healthReportWrapper.uninit.bind(healthReportWrapper), false); diff --git a/mobile/android/chrome/content/aboutHealthReport.xhtml b/mobile/android/chrome/content/aboutHealthReport.xhtml deleted file mode 100644 index 73dae0380..000000000 --- a/mobile/android/chrome/content/aboutHealthReport.xhtml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ -<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > -%brandDTD; -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> -%globalDTD; -<!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd" > -%aboutHealthReportDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>&abouthealth.pagetitle;</title> - <link rel="icon" type="image/png" sizes="64x64" - href="chrome://branding/content/favicon64.png" /> - <link rel="stylesheet" - href="chrome://browser/skin/aboutHealthReport.css" - type="text/css" /> - <script type="text/javascript;version=1.8" - src="chrome://browser/content/aboutHealthReport.js" /> - </head> - <body> - <iframe id="remote-report"/> - </body> -</html> diff --git a/mobile/android/chrome/content/aboutHome.xhtml b/mobile/android/chrome/content/aboutHome.xhtml deleted file mode 100644 index 692eac2ec..000000000 --- a/mobile/android/chrome/content/aboutHome.xhtml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html [ - <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> - %htmlDTD; - <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > - %brandDTD; - <!ENTITY % abouthomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd"> - %abouthomeDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> - <title>&abouthome.title;</title> -</head> -<body> -</body> -</html> diff --git a/mobile/android/chrome/content/aboutLogins.js b/mobile/android/chrome/content/aboutLogins.js deleted file mode 100644 index b3d003875..000000000 --- a/mobile/android/chrome/content/aboutLogins.js +++ /dev/null @@ -1,517 +0,0 @@ -/* 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/. */ - -var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; - -Cu.import("resource://services-common/utils.js"); /*global: CommonUtils */ -Cu.import("resource://gre/modules/Messaging.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyGetter(window, "gChromeWin", () => - window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow) - .QueryInterface(Ci.nsIDOMChromeWindow)); - -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Prompt", - "resource://gre/modules/Prompt.jsm"); - -var debug = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "AboutLogins"); - -var gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutLogins.properties"); - -function copyStringShowSnackbar(string, notifyString) { - try { - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); - clipboard.copyString(string); - Snackbars.show(notifyString, Snackbars.LENGTH_LONG); - } catch (e) { - debug("Error copying from about:logins"); - Snackbars.show(gStringBundle.GetStringFromName("loginsDetails.copyFailed"), Snackbars.LENGTH_LONG); - } -} - -// Delay filtering while typing in MS -const FILTER_DELAY = 500; - -var Logins = { - _logins: [], - _filterTimer: null, - _selectedLogin: null, - - // Load the logins list, displaying interstitial UI (see - // #logins-list-loading-body) while loading. There are careful - // jank-avoiding measures taken in this function; be careful when - // modifying it! - // - // Returns a Promise that resolves to the list of logins, ordered by - // hostname. - _promiseLogins: function() { - let contentBody = document.getElementById("content-body"); - let emptyBody = document.getElementById("empty-body"); - let filterIcon = document.getElementById("filter-button"); - - let showSpinner = () => { - this._toggleListBody(true); - emptyBody.classList.add("hidden"); - }; - - let getAllLogins = () => { - let logins = []; - try { - logins = Services.logins.getAllLogins(); - } catch(e) { - // It's likely that the Master Password was not entered; give - // a hint to the next person. - throw new Error("Possible Master Password permissions error: " + e.toString()); - } - - logins.sort((a, b) => a.hostname.localeCompare(b.hostname)); - - return logins; - }; - - let hideSpinner = (logins) => { - this._toggleListBody(false); - - if (!logins.length) { - contentBody.classList.add("hidden"); - filterIcon.classList.add("hidden"); - emptyBody.classList.remove("hidden"); - } else { - contentBody.classList.remove("hidden"); - emptyBody.classList.add("hidden"); - } - - return logins; - }; - - // Return a promise that is resolved after a paint. - let waitForPaint = () => { - // We're changing 'display'. We need to wait for the new value to take - // effect; otherwise, we'll block and never paint a change. Since - // requestAnimationFrame callback is generally triggered *before* any - // style flush and layout, we wait for two animation frames. This - // approach was cribbed from - // https://dxr.mozilla.org/mozilla-central/rev/5abe3c4deab94270440422c850bbeaf512b1f38d/browser/base/content/browser-fullScreen.js?offset=0#469. - return new Promise(function(resolve, reject) { - requestAnimationFrame(() => { - requestAnimationFrame(() => { - resolve(); - }); - }); - }); - }; - - // getAllLogins janks the main-thread. We need to paint before that jank; - // by throwing the janky load onto the next tick, we paint the spinner; the - // spinner is CSS animated off-main-thread. - return Promise.resolve() - .then(showSpinner) - .then(waitForPaint) - .then(getAllLogins) - .then(hideSpinner); - }, - - // Reload the logins list, displaying interstitial UI while loading. - // Update the stored and displayed list upon completion. - _reloadList: function() { - this._promiseLogins() - .then((logins) => { - this._logins = logins; - this._loadList(logins); - }) - .catch((e) => { - // There's no way to recover from errors, sadly. Log and make - // it obvious that something is up. - this._logins = []; - debug("Failed to _reloadList!"); - Cu.reportError(e); - }); - }, - - _toggleListBody: function(isLoading) { - let contentBody = document.getElementById("content-body"); - let loadingBody = document.getElementById("logins-list-loading-body"); - - if (isLoading) { - contentBody.classList.add("hidden"); - loadingBody.classList.remove("hidden"); - } else { - loadingBody.classList.add("hidden"); - contentBody.classList.remove("hidden"); - } - }, - - init: function () { - window.addEventListener("popstate", this , false); - - Services.obs.addObserver(this, "passwordmgr-storage-changed", false); - document.getElementById("update-btn").addEventListener("click", this._onSaveEditLogin.bind(this), false); - document.getElementById("password-btn").addEventListener("click", this._onPasswordBtn.bind(this), false); - - let filterInput = document.getElementById("filter-input"); - let filterContainer = document.getElementById("filter-input-container"); - - filterInput.addEventListener("input", (event) => { - // Stop any in-progress filter timer - if (this._filterTimer) { - clearTimeout(this._filterTimer); - this._filterTimer = null; - } - - // Start a new timer - this._filterTimer = setTimeout(() => { - this._filter(event); - }, FILTER_DELAY); - }, false); - - filterInput.addEventListener("blur", (event) => { - filterContainer.setAttribute("hidden", true); - }); - - document.getElementById("filter-button").addEventListener("click", (event) => { - filterContainer.removeAttribute("hidden"); - filterInput.focus(); - }, false); - - document.getElementById("filter-clear").addEventListener("click", (event) => { - // Stop any in-progress filter timer - if (this._filterTimer) { - clearTimeout(this._filterTimer); - this._filterTimer = null; - } - - filterInput.blur(); - filterInput.value = ""; - this._loadList(this._logins); - }, false); - - this._showList(); - - this._updatePasswordBtn(true); - - this._reloadList(); - }, - - uninit: function () { - Services.obs.removeObserver(this, "passwordmgr-storage-changed"); - window.removeEventListener("popstate", this, false); - }, - - _loadList: function (logins) { - let list = document.getElementById("logins-list"); - let newList = list.cloneNode(false); - - logins.forEach(login => { - let item = this._createItemForLogin(login); - newList.appendChild(item); - }); - - list.parentNode.replaceChild(newList, list); - }, - - _showList: function () { - let loginsListPage = document.getElementById("logins-list-page"); - loginsListPage.classList.remove("hidden"); - - let editLoginPage = document.getElementById("edit-login-page"); - editLoginPage.classList.add("hidden"); - - // If the Show/Hide password button has been flipped, reset it - if (this._isPasswordBtnInHideMode()) { - this._updatePasswordBtn(true); - } - }, - - _onPopState: function (event) { - // Called when back/forward is used to change the state of the page - if (event.state) { - this._showEditLoginDialog(event.state.id); - } else { - this._selectedLogin = null; - this._showList(); - } - }, - _showEditLoginDialog: function (login) { - let listPage = document.getElementById("logins-list-page"); - listPage.classList.add("hidden"); - - let editLoginPage = document.getElementById("edit-login-page"); - editLoginPage.classList.remove("hidden"); - - let usernameField = document.getElementById("username"); - usernameField.value = login.username; - let passwordField = document.getElementById("password"); - passwordField.value = login.password; - let domainField = document.getElementById("hostname"); - domainField.value = login.hostname; - - let img = document.getElementById("favicon"); - this._loadFavicon(img, login.hostname); - - let headerText = document.getElementById("edit-login-header-text"); - if (login.hostname && (login.hostname != "")) { - headerText.textContent = login.hostname; - } - else { - headerText.textContent = gStringBundle.GetStringFromName("editLogin.fallbackTitle"); - } - - passwordField.addEventListener("input", (event) => { - let newPassword = passwordField.value; - let updateBtn = document.getElementById("update-btn"); - - if (newPassword === "") { - updateBtn.disabled = true; - updateBtn.classList.add("disabled-btn"); - } else if ((newPassword !== "") && (updateBtn.disabled === true)) { - updateBtn.disabled = false; - updateBtn.classList.remove("disabled-btn"); - } - }, false); - }, - - _onSaveEditLogin: function() { - let newUsername = document.getElementById("username").value; - let newPassword = document.getElementById("password").value; - let newDomain = document.getElementById("hostname").value; - let origUsername = this._selectedLogin.username; - let origPassword = this._selectedLogin.password; - let origDomain = this._selectedLogin.hostname; - - try { - if ((newUsername === origUsername) && - (newPassword === origPassword) && - (newDomain === origDomain) ) { - Snackbars.show(gStringBundle.GetStringFromName("editLogin.saved1"), Snackbars.LENGTH_LONG); - this._showList(); - return; - } - - let logins = Services.logins.findLogins({}, origDomain, origDomain, null); - - for (let i = 0; i < logins.length; i++) { - if (logins[i].username == origUsername) { - let clone = logins[i].clone(); - clone.username = newUsername; - clone.password = newPassword; - clone.hostname = newDomain; - Services.logins.removeLogin(logins[i]); - Services.logins.addLogin(clone); - break; - } - } - } catch (e) { - Snackbars.show(gStringBundle.GetStringFromName("editLogin.couldNotSave"), Snackbars.LENGTH_LONG); - return; - } - Snackbars.show(gStringBundle.GetStringFromName("editLogin.saved1"), Snackbars.LENGTH_LONG); - this._showList(); - }, - - _onPasswordBtn: function () { - this._updatePasswordBtn(this._isPasswordBtnInHideMode()); - }, - - _updatePasswordBtn: function (aShouldShow) { - let passwordField = document.getElementById("password"); - let button = document.getElementById("password-btn"); - let show = gStringBundle.GetStringFromName("password-btn.show"); - let hide = gStringBundle.GetStringFromName("password-btn.hide"); - if (aShouldShow) { - passwordField.type = "password"; - button.textContent = show; - button.classList.remove("password-btn-hide"); - } else { - passwordField.type = "text"; - button.textContent= hide; - button.classList.add("password-btn-hide"); - } - }, - - _isPasswordBtnInHideMode: function () { - let button = document.getElementById("password-btn"); - return button.classList.contains("password-btn-hide"); - }, - - _showPassword: function(password) { - let passwordPrompt = new Prompt({ - window: window, - message: password, - buttons: [ - gStringBundle.GetStringFromName("loginsDialog.copy"), - gStringBundle.GetStringFromName("loginsDialog.cancel") ] - }).show((data) => { - switch (data.button) { - case 0: - // Corresponds to "Copy password" button. - copyStringShowSnackbar(password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied")); - } - }); - }, - - _onLoginClick: function (event) { - let loginItem = event.currentTarget; - let login = loginItem.login; - if (!login) { - debug("No login!"); - return; - } - - let prompt = new Prompt({ - window: window, - }); - let menuItems = [ - { label: gStringBundle.GetStringFromName("loginsMenu.showPassword") }, - { label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") }, - { label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") }, - { label: gStringBundle.GetStringFromName("loginsMenu.editLogin") }, - { label: gStringBundle.GetStringFromName("loginsMenu.delete") } - ]; - - prompt.setSingleChoiceItems(menuItems); - prompt.show((data) => { - // Switch on indices of buttons, as they were added when creating login item. - switch (data.button) { - case 0: - this._showPassword(login.password); - break; - case 1: - copyStringShowSnackbar(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied")); - break; - case 2: - copyStringShowSnackbar(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied")); - break; - case 3: - this._selectedLogin = login; - this._showEditLoginDialog(login); - history.pushState({ id: login.guid }, document.title); - break; - case 4: - let confirmPrompt = new Prompt({ - window: window, - message: gStringBundle.GetStringFromName("loginsDialog.confirmDelete"), - buttons: [ - gStringBundle.GetStringFromName("loginsDialog.confirm"), - gStringBundle.GetStringFromName("loginsDialog.cancel") ] - }); - confirmPrompt.show((data) => { - switch (data.button) { - case 0: - // Corresponds to "confirm" button. - Services.logins.removeLogin(login); - } - }); - } - }); - }, - - _loadFavicon: function (aImg, aHostname) { - // Load favicon from cache. - Messaging.sendRequestForResult({ - type: "Favicon:CacheLoad", - url: aHostname, - }).then(function(faviconUrl) { - aImg.style.backgroundImage= "url('" + faviconUrl + "')"; - aImg.style.visibility = "visible"; - }, function(data) { - debug("Favicon cache failure : " + data); - aImg.style.visibility = "visible"; - }); - }, - - _createItemForLogin: function (login) { - let loginItem = document.createElement("div"); - - loginItem.setAttribute("loginID", login.guid); - loginItem.className = "login-item list-item"; - - loginItem.addEventListener("click", this, true); - - // Create item icon. - let img = document.createElement("div"); - img.className = "icon"; - - this._loadFavicon(img, login.hostname); - loginItem.appendChild(img); - - // Create item details. - let inner = document.createElement("div"); - inner.className = "inner"; - - let details = document.createElement("div"); - details.className = "details"; - inner.appendChild(details); - - let titlePart = document.createElement("div"); - titlePart.className = "hostname"; - titlePart.textContent = login.hostname; - details.appendChild(titlePart); - - let versionPart = document.createElement("div"); - versionPart.textContent = login.httpRealm; - versionPart.className = "realm"; - details.appendChild(versionPart); - - let descPart = document.createElement("div"); - descPart.textContent = login.username; - descPart.className = "username"; - inner.appendChild(descPart); - - loginItem.appendChild(inner); - loginItem.login = login; - return loginItem; - }, - - handleEvent: function (event) { - switch (event.type) { - case "popstate": { - this._onPopState(event); - break; - } - case "click": { - this._onLoginClick(event); - break; - } - } - }, - - observe: function (subject, topic, data) { - switch(topic) { - case "passwordmgr-storage-changed": { - this._reloadList(); - break; - } - } - }, - - _filter: function(event) { - let value = event.target.value.toLowerCase(); - let logins = this._logins.filter((login) => { - if (login.hostname.toLowerCase().indexOf(value) != -1) { - return true; - } - if (login.username && - login.username.toLowerCase().indexOf(value) != -1) { - return true; - } - if (login.httpRealm && - login.httpRealm.toLowerCase().indexOf(value) != -1) { - return true; - } - return false; - }); - - this._loadList(logins); - } -}; - -window.addEventListener("load", Logins.init.bind(Logins), false); -window.addEventListener("unload", Logins.uninit.bind(Logins), false); diff --git a/mobile/android/chrome/content/aboutLogins.xhtml b/mobile/android/chrome/content/aboutLogins.xhtml deleted file mode 100644 index 02225d43a..000000000 --- a/mobile/android/chrome/content/aboutLogins.xhtml +++ /dev/null @@ -1,90 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" -"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" > -%globalDTD; -<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > -%brandDTD; -<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutLogins.dtd" > -%aboutDTD; -]> -<!-- 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/. --> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <title>&aboutLogins.title;</title> - <meta name="viewport" content="width=device-width; user-scalable=0" /> - <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" /> - <link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/> - <link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/> - <link rel="stylesheet" href="chrome://browser/skin/aboutLogins.css" type="text/css"/> - <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutLogins.js"></script> - </head> - <body dir="&locale.dir;"> - - <div id="logins-list-page"> - <div id="logins-header" class="header"> - <div>&aboutLogins.title;</div> - <ul class="toolbar-buttons"> - <li id="filter-button"></li> - </ul> - </div> - <div id="content-body"> - <div id="logins-list" class="list"/> - <div id="filter-input-container" hidden="true"> - <input id="filter-input" type="search"/> - <div id="filter-clear"/> - </div> - </div> - <div id="logins-list-loading-body" class="hidden"> - <div id="loading-img-container"> - - <div id="spinner" class="mui-refresh-main"> - <div class="mui-refresh-wrapper"> - <div class="mui-spinner-wrapper"> - <div class="mui-spinner-main"> - <div class="mui-spinner-left"> - <div class="mui-half-circle-left" /> - </div> - <div class="mui-spinner-right"> - <div class="mui-half-circle-right" /> - </div> - </div> - </div> - </div> - </div> - - </div> - </div> - <div id="empty-body" class="hidden"> - <div id="empty-obj-text-container"> - <object type="image/svg+xml" id="empty-icon" data="chrome://browser/skin/images/icon_key_emptypage.svg"/> - <div class="empty-text">&aboutLogins.emptyLoginText;</div> - <div class="empty-hint">&aboutLogins.emptyLoginHint;</div> - </div> - </div> - </div> - - <div id="edit-login-page" class="hidden"> - <div id="edit-login-header" class="header"> - <div id="edit-login-header-text"/> - </div> - <div class="edit-login-div"> - <div id="favicon" class="edit-login-icon"/> - <input type="text" name="hostname" id="hostname" class="edit-login-input"/> - </div> - <div class="edit-login-div"> - <input type="text" name="username" id="username" class="edit-login-input" autocomplete="off"/> - </div> - <div class="edit-login-div"> - <input type="password" id="password" name="password" value="password" class="edit-login-input" /> - <button id="password-btn"></button> - </div> - <div class="edit-login-div"> - <button id="update-btn" class="update-button">&aboutLogins.update;</button> - </div> - </div> - - </body> -</html> diff --git a/mobile/android/chrome/content/aboutPrivateBrowsing.js b/mobile/android/chrome/content/aboutPrivateBrowsing.js deleted file mode 100644 index 782abfb5d..000000000 --- a/mobile/android/chrome/content/aboutPrivateBrowsing.js +++ /dev/null @@ -1,32 +0,0 @@ -/* 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"; - -var Ci = Components.interfaces; -var Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); - -XPCOMUtils.defineLazyGetter(window, "gChromeWin", () => - window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow) - .QueryInterface(Ci.nsIDOMChromeWindow)); - -document.addEventListener("DOMContentLoaded", function() { - let BrowserApp = gChromeWin.BrowserApp; - - if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) { - document.body.setAttribute("class", "normal"); - document.getElementById("newPrivateTabLink").addEventListener("click", function() { - BrowserApp.addTab("about:privatebrowsing", { selected: true, parentId: BrowserApp.selectedTab.id, isPrivate: true }); - }, false); - } - }, false); diff --git a/mobile/android/chrome/content/aboutPrivateBrowsing.xhtml b/mobile/android/chrome/content/aboutPrivateBrowsing.xhtml deleted file mode 100644 index 7075bd11e..000000000 --- a/mobile/android/chrome/content/aboutPrivateBrowsing.xhtml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -# 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/. ---> -<!DOCTYPE html [ - <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> - %htmlDTD; - <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > - %brandDTD; - <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> - %globalDTD; - <!ENTITY % privatebrowsingpageDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd"> - %privatebrowsingpageDTD; -]> - -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <title>&privatebrowsingpage.title;</title> - <meta name="viewport" content="width=device-width, initial-scale=1; user-scalable=no"/> - <link rel="stylesheet" href="chrome://browser/skin/aboutPrivateBrowsing.css" type="text/css" media="all"/> - <link rel="icon" type="image/png" href="chrome://branding/content/favicon32.png" /> - <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutPrivateBrowsing.js"></script> - </head> - - <body class="private"> - <img class="showPrivate masq" src="chrome://browser/skin/images/privatebrowsing-mask-and-shield.svg" /> - <img class="showNormal masq" src="chrome://browser/skin/images/privatebrowsing-mask.png" /> - - <h1 class="showPrivate">&privatebrowsingpage.title;<br />&privatebrowsingpage.title.private;</h1> - <h1 class="showNormal">&privatebrowsingpage.title.normal1;</h1> - - <div class="contentSection"> - <p class="showPrivate">&privatebrowsingpage.description.trackingProtection;<br /><br />&privatebrowsingpage.description.privateDetails;</p> - <p class="showNormal">&privatebrowsingpage.description.normal2;</p> - - <p class="showPrivate"><a href="https://support.mozilla.org/kb/private-browsing-firefox-android">&privatebrowsingpage.link.private;</a></p> - <p class="showNormal"><a href="#" id="newPrivateTabLink">&privatebrowsingpage.link.normal;</a></p> - </div> - - </body> -</html> diff --git a/mobile/android/chrome/content/aboutRights.xhtml b/mobile/android/chrome/content/aboutRights.xhtml deleted file mode 100644 index 8172788f4..000000000 --- a/mobile/android/chrome/content/aboutRights.xhtml +++ /dev/null @@ -1,95 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html [ - <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> - %htmlDTD; - <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> - %brandDTD; - <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd"> - %aboutRightsDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml"> - -<head> - <title>&rights.pagetitle;</title> - <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/> -</head> - -<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer"> - -<h1>&rights.intro-header;</h1> - -<p>&rights.intro;</p> - -<ul> - <li>&rights.intro-point1a;<a href="https://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li> - <!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded. - Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace) - Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) --> - <li>&rights.intro-point2-a;<a href="https://www.mozilla.org/foundation/trademarks/policy.html">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li> - <li>&rights.intro-point2.5;</li> - <li>&rights2.intro-point3a;<a href="https://www.mozilla.org/privacy/firefox/">&rights2.intro-point3b;</a>&rights.intro-point3c;</li> - <li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li> -</ul> - -<div id="webservices-container"> - <a name="webservices"/> - <h3>&rights2.webservices-header;</h3> - - <p>&rights2.webservices-a;<a href="about:rights#disabling-webservices" onclick="showDisablingServices();">&rights2.webservices-b;</a>&rights3.webservices-c;</p> - - <div id="disabling-webservices-container" style="margin-left:40px;"> - <a name="disabling-webservices"/> - <!-- Safe Browsing cannot be disabled in Firefox Mobile; these instructions show how to do it on desktop - <p><strong>&rights.safebrowsing-a;</strong>&rights.safebrowsing-b;</p> - <ul> - <li>&rights.safebrowsing-term1;</li> - <li>&rights.safebrowsing-term2;</li> - <li>&rights2.safebrowsing-term3;</li> - <li>&rights.safebrowsing-term4;</li> - </ul> - --> - - <p><strong>&rights.locationawarebrowsing-a;</strong>&rights.locationawarebrowsing-b;</p> - <ul> - <li>&rights.locationawarebrowsing-term1a;<code>&rights.locationawarebrowsing-term1b;</code></li> - <li>&rights.locationawarebrowsing-term2;</li> - <li>&rights.locationawarebrowsing-term3;</li> - <li>&rights.locationawarebrowsing-term4;</li> - </ul> - </div> - - <ol> - <!-- Terms only apply to official builds, unbranded builds get a placeholder. --> - <li>&rights2.webservices-term1;</li> - <li>&rights.webservices-term2;</li> - <li>&rights2.webservices-term3;</li> - <li><strong>&rights.webservices-term4;</strong></li> - <li><strong>&rights.webservices-term5;</strong></li> - <li>&rights.webservices-term6;</li> - <li>&rights.webservices-term7;</li> - </ol> -</div> - -<script type="application/javascript"><![CDATA[ - var servicesDiv = document.getElementById("webservices-container"); - servicesDiv.style.display = "none"; - - function showServices() { - servicesDiv.style.display = ""; - } - - var disablingServicesDiv = document.getElementById("disabling-webservices-container"); - disablingServicesDiv.style.display = "none"; - - function showDisablingServices() { - disablingServicesDiv.style.display = ""; - } -]]></script> - -</body> -</html> diff --git a/mobile/android/chrome/content/bindings/checkbox.xml b/mobile/android/chrome/content/bindings/checkbox.xml deleted file mode 100644 index ec5c7828b..000000000 --- a/mobile/android/chrome/content/bindings/checkbox.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0"?> - -<!-- 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/. --> - -<!DOCTYPE bindings [ - <!ENTITY % checkboxDTD SYSTEM "chrome://browser/locale/checkbox.dtd"> - %checkboxDTD; -]> - -<bindings - xmlns="http://www.mozilla.org/xbl" - xmlns:xbl="http://www.mozilla.org/xbl" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <binding id="checkbox-radio" display="xul:box" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline"> - <content> - <xul:radiogroup anonid="group" xbl:inherits="disabled"> - <xul:radio anonid="on" class="checkbox-radio-on" label="&checkbox.yes.label;" xbl:inherits="label=onlabel"/> - <xul:radio anonid="off" class="checkbox-radio-off" label="&checkbox.no.label;" xbl:inherits="label=offlabel"/> - </xul:radiogroup> - </content> - <implementation> - <constructor><![CDATA[ - this.setChecked(this.checked); - ]]></constructor> - - <field name="_group"> - document.getAnonymousElementByAttribute(this, "anonid", "group"); - </field> - - <field name="_on"> - document.getAnonymousElementByAttribute(this, "anonid", "on"); - </field> - - <field name="_off"> - document.getAnonymousElementByAttribute(this, "anonid", "off"); - </field> - - <property name="onlabel" - onget="return this._on.label" - onset="this._on.label=val"/> - - <property name="offlabel" - onget="return this._off.label" - onset="this._off.label=val"/> - - <method name="setChecked"> - <parameter name="aValue"/> - <body> - <![CDATA[ - var change = (aValue != this.checked); - if (aValue) { - this.setAttribute("checked", "true"); - this._group.selectedItem = this._on; - } - else { - this.removeAttribute("checked"); - this._group.selectedItem = this._off; - } - - if (change) { - var event = document.createEvent("Events"); - event.initEvent("CheckboxStateChange", true, true); - this.dispatchEvent(event); - } - return aValue; - ]]> - </body> - </method> - </implementation> - </binding> - -</bindings> diff --git a/mobile/android/chrome/content/bindings/settings.xml b/mobile/android/chrome/content/bindings/settings.xml deleted file mode 100644 index 0019e9d3b..000000000 --- a/mobile/android/chrome/content/bindings/settings.xml +++ /dev/null @@ -1,66 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> - - -<bindings - xmlns="http://www.mozilla.org/xbl" - xmlns:xbl="http://www.mozilla.org/xbl" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <binding id="setting-fulltoggle-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool"> - <handlers> - <handler event="command" button="0" phase="capturing"> - <![CDATA[ - event.stopPropagation(); - ]]> - </handler> - <handler event="click" button="0" phase="capturing"> - <![CDATA[ - event.stopPropagation(); - this.input.checked = !this.input.checked; - this.inputChanged(); - this.fireEvent("oncommand"); - ]]> - </handler> - </handlers> - </binding> - - <binding id="setting-fulltoggle-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-boolint"> - <handlers> - <handler event="command" button="0" phase="capturing"> - <![CDATA[ - event.stopPropagation(); - ]]> - </handler> - <handler event="click" button="0" phase="capturing"> - <![CDATA[ - event.stopPropagation(); - this.input.checked = !this.input.checked; - this.inputChanged(); - this.fireEvent("oncommand"); - ]]> - </handler> - </handlers> - </binding> - - <binding id="setting-fulltoggle-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-localized-bool"> - <handlers> - <handler event="command" button="0" phase="capturing"> - <![CDATA[ - event.stopPropagation(); - ]]> - </handler> - <handler event="click" button="0" phase="capturing"> - <![CDATA[ - event.stopPropagation(); - this.input.checked = !this.input.checked; - this.inputChanged(); - this.fireEvent("oncommand"); - ]]> - </handler> - </handlers> - </binding> -</bindings> diff --git a/mobile/android/chrome/content/blockedSite.xhtml b/mobile/android/chrome/content/blockedSite.xhtml deleted file mode 100644 index 5f04edbef..000000000 --- a/mobile/android/chrome/content/blockedSite.xhtml +++ /dev/null @@ -1,195 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html [ - <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> - %htmlDTD; - <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> - %globalDTD; - <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > - %brandDTD; - <!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/phishing.dtd"> - %blockedSiteDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist"> - <head> - <meta name="viewport" content="width=device-width; user-scalable=false" /> - <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" /> - <link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/blocked-warning.png"/> - - <script type="application/javascript"><![CDATA[ - // Error url MUST be formatted like this: - // about:blocked?e=error_code&u=url(&o=1)? - // (o=1 when user overrides are allowed) - - // Note that this file uses document.documentURI to get - // the URL (with the format from above). This is because - // document.location.href gets the current URI off the docshell, - // which is the URL displayed in the location bar, i.e. - // the URI that the user attempted to load. - - function getErrorCode() - { - var url = document.documentURI; - var error = url.search(/e\=/); - var duffUrl = url.search(/\&u\=/); - return decodeURIComponent(url.slice(error + 2, duffUrl)); - } - - function getURL() - { - var url = document.documentURI; - var match = url.match(/&u=([^&]+)&/); - - // match == null if not found; if so, return an empty string - // instead of what would turn out to be portions of the URI - if (!match) - return ""; - - url = decodeURIComponent(match[1]); - - // If this is a view-source page, then get then real URI of the page - if (/^view-source\:/.test(url)) - url = url.slice(12); - return url; - } - - /** - * Check whether this warning page should be overridable or whether - * the "ignore warning" button should be hidden. - */ - function getOverride() - { - var url = document.documentURI; - var match = url.match(/&o=1&/); - return !!match; - } - - /** - * Attempt to get the hostname via document.location. Fail back - * to getURL so that we always return something meaningful. - */ - function getHostString() - { - try { - return document.location.hostname; - } catch (e) { - return getURL(); - } - } - - function initPage() - { - var error = ""; - switch (getErrorCode()) { - case "malwareBlocked" : - error = "malware"; - break; - case "deceptiveBlocked" : - error = "phishing"; - break; - case "unwantedBlocked" : - error = "unwanted"; - break; - default: - return; - } - - var el; - - if (error !== "malware") { - el = document.getElementById("errorTitleText_malware"); - el.parentNode.removeChild(el); - el = document.getElementById("errorShortDescText_malware"); - el.parentNode.removeChild(el); - el = document.getElementById("errorLongDescText_malware"); - el.parentNode.removeChild(el); - } - - if (error !== "phishing") { - el = document.getElementById("errorTitleText_phishing"); - el.parentNode.removeChild(el); - el = document.getElementById("errorShortDescText_phishing"); - el.parentNode.removeChild(el); - el = document.getElementById("errorLongDescText_phishing"); - el.parentNode.removeChild(el); - } - - if (error !== "unwanted") { - el = document.getElementById("errorTitleText_unwanted"); - el.parentNode.removeChild(el); - el = document.getElementById("errorShortDescText_unwanted"); - el.parentNode.removeChild(el); - el = document.getElementById("errorLongDescText_unwanted"); - el.parentNode.removeChild(el); - } - - if (!getOverride()) { - var btn = document.getElementById("ignoreWarningButton"); - if (btn) { - btn.parentNode.removeChild(btn); - } - } - - // Set sitename - document.getElementById(error + "_sitename").textContent = getHostString(); - document.title = document.getElementById("errorTitleText_" + error) - .innerHTML; - - // Inform the test harness that we're done loading the page - var event = new CustomEvent("AboutBlockedLoaded"); - document.dispatchEvent(event); - } - ]]></script> - </head> - - <body id="errorPage" class="blockedsite" dir="&locale.dir;"> - - <div id="errorPageContainer"> - - <!-- Error Title --> - <div id="errorTitle"> - <h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title3;</h1> - <h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1> - <h1 id="errorTitleText_unwanted" class="errorTitleText">&safeb.blocked.unwantedPage.title;</h1> - </div> - - <div id="errorLongContent"> - - <!-- Short Description --> - <div id="errorShortDesc"> - <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc3;</p> - <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p> - <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p> - </div> - - <!-- Long Description --> - <div id="errorLongDesc"> - <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc3;</p> - <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p> - <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p> - </div> - - <!-- Action buttons --> - <div id="buttons"> - <!-- Commands handled in browser.js --> - <button id="getMeOutButton">&safeb.palm.accept.label;</button> - <button id="reportButton">&safeb.palm.reportPage.label;</button> - </div> - </div> - <div id="ignoreWarning"> - <button id="ignoreWarningButton">&safeb.palm.decline.label;</button> - </div> - </div> - <!-- - - Note: It is important to run the script this way, instead of using - - an onload handler. This is because error pages are loaded as - - LOAD_BACKGROUND, which means that onload handlers will not be executed. - --> - <script type="application/javascript">initPage();</script> - </body> -</html> diff --git a/mobile/android/chrome/content/browser.css b/mobile/android/chrome/content/browser.css deleted file mode 100644 index fdc35d961..000000000 --- a/mobile/android/chrome/content/browser.css +++ /dev/null @@ -1,7 +0,0 @@ -/* 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/. */ - -browser[remote="true"] { - -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser"); -}
\ No newline at end of file diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js deleted file mode 100644 index 535f7e607..000000000 --- a/mobile/android/chrome/content/browser.js +++ /dev/null @@ -1,6961 +0,0 @@ -// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- -/* 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"; - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; -var Cr = Components.results; - -Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource://gre/modules/AsyncPrefs.jsm"); -Cu.import("resource://gre/modules/DelayedInit.jsm"); - -if (AppConstants.ACCESSIBILITY) { - XPCOMUtils.defineLazyModuleGetter(this, "AccessFu", - "resource://gre/modules/accessibility/AccessFu.jsm"); -} - -XPCOMUtils.defineLazyModuleGetter(this, "SpatialNavigation", - "resource://gre/modules/SpatialNavigation.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadNotifications", - "resource://gre/modules/DownloadNotifications.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "JNI", - "resource://gre/modules/JNI.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", - "resource://gre/modules/UITelemetry.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", - "resource://gre/modules/PluralForm.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Downloads", - "resource://gre/modules/Downloads.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Messaging", - "resource://gre/modules/Messaging.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides", - "resource://gre/modules/UserAgentOverrides.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", - "resource://gre/modules/LoginManagerContent.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent", - "resource://gre/modules/LoginManagerParent.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); - -#ifdef MOZ_SAFE_BROWSING - XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", - "resource://gre/modules/SafeBrowsing.jsm"); -#endif - -XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", - "resource://gre/modules/BrowserUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer", - "resource://gre/modules/Sanitizer.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Prompt", - "resource://gre/modules/Prompt.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "HelperApps", - "resource://gre/modules/HelperApps.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "SSLExceptions", - "resource://gre/modules/SSLExceptions.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", - "resource://gre/modules/FormHistory.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "uuidgen", - "@mozilla.org/uuid-generator;1", - "nsIUUIDGenerator"); - -XPCOMUtils.defineLazyServiceGetter(this, "Profiler", - "@mozilla.org/tools/profiler;1", - "nsIProfiler"); - -XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery", - "resource://gre/modules/SimpleServiceDiscovery.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", - "resource://gre/modules/CharsetMenu.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "NetErrorHelper", - "resource://gre/modules/NetErrorHelper.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils", - "resource://gre/modules/PermissionsUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Preferences", - "resource://gre/modules/Preferences.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences", - "resource://gre/modules/SharedPreferences.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Notifications", - "resource://gre/modules/Notifications.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "WebsiteMetadata", "resource://gre/modules/WebsiteMetadata.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator", - "@mozilla.org/gfx/fontenumerator;1", - "nsIFontEnumerator"); - -var lazilyLoadedBrowserScripts = [ - ["SelectHelper", "chrome://browser/content/SelectHelper.js"], - ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"], - ["MasterPassword", "chrome://browser/content/MasterPassword.js"], - ["PluginHelper", "chrome://browser/content/PluginHelper.js"], - ["OfflineApps", "chrome://browser/content/OfflineApps.js"], - ["Linkifier", "chrome://browser/content/Linkify.js"], - ["CastingApps", "chrome://browser/content/CastingApps.js"], - ["RemoteDebugger", "chrome://browser/content/RemoteDebugger.js"], -]; -if (!AppConstants.RELEASE_OR_BETA) { - lazilyLoadedBrowserScripts.push( - ["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"]); -} - -lazilyLoadedBrowserScripts.forEach(function (aScript) { - let [name, script] = aScript; - XPCOMUtils.defineLazyGetter(window, name, function() { - let sandbox = {}; - Services.scriptloader.loadSubScript(script, sandbox); - return sandbox[name]; - }); -}); - -var lazilyLoadedObserverScripts = [ - ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"], - ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"], - ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"], - ["PermissionsHelper", ["Permissions:Check", "Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"], - ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"], - ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"], - ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"], - ["Reader", ["Reader:AddToCache", "Reader:RemoveFromCache"], "chrome://browser/content/Reader.js"], - ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"], -]; - -lazilyLoadedObserverScripts.push( -["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"], - "chrome://browser/content/ActionBarHandler.js"] -); - -if (AppConstants.MOZ_WEBRTC) { - lazilyLoadedObserverScripts.push( - ["WebrtcUI", ["getUserMedia:request", - "PeerConnection:request", - "recording-device-events", - "VideoCapture:Paused", - "VideoCapture:Resumed"], "chrome://browser/content/WebrtcUI.js"]) -} - -lazilyLoadedObserverScripts.forEach(function (aScript) { - let [name, notifications, script] = aScript; - XPCOMUtils.defineLazyGetter(window, name, function() { - let sandbox = {}; - Services.scriptloader.loadSubScript(script, sandbox); - return sandbox[name]; - }); - let observer = (s, t, d) => { - Services.obs.removeObserver(observer, t); - Services.obs.addObserver(window[name], t, false); - window[name].observe(s, t, d); // Explicitly notify new observer - }; - notifications.forEach((notification) => { - Services.obs.addObserver(observer, notification, false); - }); -}); - -// Lazily-loaded browser scripts that use message listeners. -[ - ["Reader", [ - ["Reader:AddToCache", false], - ["Reader:RemoveFromCache", false], - ["Reader:ArticleGet", false], - ["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection. - ["Reader:DropdownOpened", false], - ["Reader:FaviconRequest", false], - ["Reader:ToolbarHidden", false], - ["Reader:SystemUIVisibility", false], - ["Reader:UpdateReaderButton", false], - ], "chrome://browser/content/Reader.js"], -].forEach(aScript => { - let [name, messages, script] = aScript; - XPCOMUtils.defineLazyGetter(window, name, function() { - let sandbox = {}; - Services.scriptloader.loadSubScript(script, sandbox); - return sandbox[name]; - }); - - let mm = window.getGroupMessageManager("browsers"); - let listener = (message) => { - mm.removeMessageListener(message.name, listener); - let listenAfterClose = false; - for (let [name, laClose] of messages) { - if (message.name === name) { - listenAfterClose = laClose; - break; - } - } - - mm.addMessageListener(message.name, window[name], listenAfterClose); - window[name].receiveMessage(message); - }; - - messages.forEach((message) => { - let [name, listenAfterClose] = message; - mm.addMessageListener(name, listener, listenAfterClose); - }); -}); - -// Lazily-loaded JS modules that use observer notifications -[ - ["Home", ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate", "HomePanels:RefreshView", - "HomePanels:Installed", "HomePanels:Uninstalled"], "resource://gre/modules/Home.jsm"], -].forEach(module => { - let [name, notifications, resource] = module; - XPCOMUtils.defineLazyModuleGetter(this, name, resource); - let observer = (s, t, d) => { - Services.obs.removeObserver(observer, t); - Services.obs.addObserver(this[name], t, false); - this[name].observe(s, t, d); // Explicitly notify new observer - }; - notifications.forEach(notification => { - Services.obs.addObserver(observer, notification, false); - }); -}); - -XPCOMUtils.defineLazyServiceGetter(this, "Haptic", - "@mozilla.org/widget/hapticfeedback;1", "nsIHapticFeedback"); - -XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls", - "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService"); - -XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils", - "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils"); - -XPCOMUtils.defineLazyServiceGetter(window, "URIFixup", - "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"); - -if (AppConstants.MOZ_WEBRTC) { - XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", - "@mozilla.org/mediaManagerService;1", "nsIMediaManagerService"); -} - -XPCOMUtils.defineLazyModuleGetter(this, "Log", - "resource://gre/modules/AndroidLog.jsm", "AndroidLog"); - -// Define the "dump" function as a binding of the Log.d function so it specifies -// the "debug" priority and a log tag. -function dump(msg) { - Log.d("Browser", msg); -} - -const kStateActive = 0x00000001; // :active pseudoclass for elements - -const kXLinkNamespace = "http://www.w3.org/1999/xlink"; - -function fuzzyEquals(a, b) { - return (Math.abs(a - b) < 1e-6); -} - -XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { - let ContentAreaUtils = {}; - Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); - return ContentAreaUtils; -}); - -XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Point", "resource://gre/modules/Geometry.jsm"); - -function resolveGeckoURI(aURI) { - if (!aURI) - throw "Can't resolve an empty uri"; - - if (aURI.startsWith("chrome://")) { - let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]); - return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec; - } else if (aURI.startsWith("resource://")) { - let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); - return handler.resolveURI(Services.io.newURI(aURI, null, null)); - } - return aURI; -} - -/** - * Cache of commonly used string bundles. - */ -var Strings = { - init: function () { - XPCOMUtils.defineLazyGetter(Strings, "brand", () => Services.strings.createBundle("chrome://branding/locale/brand.properties")); - XPCOMUtils.defineLazyGetter(Strings, "browser", () => Services.strings.createBundle("chrome://browser/locale/browser.properties")); - XPCOMUtils.defineLazyGetter(Strings, "reader", () => Services.strings.createBundle("chrome://global/locale/aboutReader.properties")); - }, - - flush: function () { - Services.strings.flushBundles(); - this.init(); - }, -}; - -Strings.init(); - -const kFormHelperModeDisabled = 0; -const kFormHelperModeEnabled = 1; -const kFormHelperModeDynamic = 2; // disabled on tablets -const kMaxHistoryListSize = 50; - -function InitLater(fn, object, name) { - return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */); -} - -var BrowserApp = { - _tabs: [], - _selectedTab: null, - - get isTablet() { - let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); - delete this.isTablet; - return this.isTablet = sysInfo.get("tablet"); - }, - - get isOnLowMemoryPlatform() { - let memory = Cc["@mozilla.org/xpcom/memory-service;1"].getService(Ci.nsIMemory); - delete this.isOnLowMemoryPlatform; - return this.isOnLowMemoryPlatform = memory.isLowMemoryPlatform(); - }, - - deck: null, - - startup: function startup() { - window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); - dump("zerdatime " + Date.now() + " - browser chrome startup finished."); - Services.obs.notifyObservers(this.browser, "BrowserChrome:Ready", null); - - this.deck = document.getElementById("browsers"); - - BrowserEventHandler.init(); - - ViewportHandler.init(); - - Services.androidBridge.browserApp = this; - - Services.obs.addObserver(this, "Locale:OS", false); - Services.obs.addObserver(this, "Locale:Changed", false); - Services.obs.addObserver(this, "Tab:Load", false); - Services.obs.addObserver(this, "Tab:Selected", false); - Services.obs.addObserver(this, "Tab:Closed", false); - Services.obs.addObserver(this, "Session:Back", false); - Services.obs.addObserver(this, "Session:Forward", false); - Services.obs.addObserver(this, "Session:Navigate", false); - Services.obs.addObserver(this, "Session:Reload", false); - Services.obs.addObserver(this, "Session:Stop", false); - Services.obs.addObserver(this, "SaveAs:PDF", false); - Services.obs.addObserver(this, "Browser:Quit", false); - Services.obs.addObserver(this, "ScrollTo:FocusedInput", false); - Services.obs.addObserver(this, "Sanitize:ClearData", false); - Services.obs.addObserver(this, "FullScreen:Exit", false); - Services.obs.addObserver(this, "Passwords:Init", false); - Services.obs.addObserver(this, "FormHistory:Init", false); - Services.obs.addObserver(this, "android-get-pref", false); - Services.obs.addObserver(this, "android-set-pref", false); - Services.obs.addObserver(this, "gather-telemetry", false); - Services.obs.addObserver(this, "keyword-search", false); - Services.obs.addObserver(this, "sessionstore-state-purge-complete", false); - Services.obs.addObserver(this, "Fonts:Reload", false); - Services.obs.addObserver(this, "Vibration:Request", false); - - Messaging.addListener(this.getHistory.bind(this), "Session:GetHistory"); - - window.addEventListener("fullscreen", function() { - Messaging.sendRequest({ - type: window.fullScreen ? "ToggleChrome:Hide" : "ToggleChrome:Show" - }); - }, false); - - window.addEventListener("fullscreenchange", (e) => { - // This event gets fired on the document and its entire ancestor chain - // of documents. When enabling fullscreen, it is fired on the top-level - // document first and goes down; when disabling the order is reversed - // (per spec). This means the last event on enabling will be for the innermost - // document, which will have fullscreenElement set correctly. - let doc = e.target; - Messaging.sendRequest({ - type: doc.fullscreenElement ? "DOMFullScreen:Start" : "DOMFullScreen:Stop", - rootElement: doc.fullscreenElement == doc.documentElement - }); - - if (this.fullscreenTransitionTab) { - // Tab selection has changed during a fullscreen transition, handle it now. - let tab = this.fullscreenTransitionTab; - this.fullscreenTransitionTab = null; - this._handleTabSelected(tab); - } - }, false); - - NativeWindow.init(); - FormAssistant.init(); - IndexedDB.init(); - XPInstallObserver.init(); - CharacterEncoding.init(); - ActivityObserver.init(); - RemoteDebugger.init(); - UserAgentOverrides.init(); - DesktopUserAgent.init(); - Distribution.init(); - Tabs.init(); - SearchEngines.init(); - Experiments.init(); - - // XXX maybe we don't do this if the launch was kicked off from external - Services.io.offline = false; - - // Broadcast a UIReady message so add-ons know we are finished with startup - let event = document.createEvent("Events"); - event.initEvent("UIReady", true, false); - window.dispatchEvent(event); - - if (this._startupStatus) { - this.onAppUpdated(); - } - - if (!ParentalControls.isAllowed(ParentalControls.INSTALL_EXTENSION)) { - // Disable extension installs - Services.prefs.setIntPref("extensions.enabledScopes", 1); - Services.prefs.setIntPref("extensions.autoDisableScopes", 1); - Services.prefs.setBoolPref("xpinstall.enabled", false); - } else if (ParentalControls.parentalControlsEnabled) { - Services.prefs.clearUserPref("extensions.enabledScopes"); - Services.prefs.clearUserPref("extensions.autoDisableScopes"); - Services.prefs.setBoolPref("xpinstall.enabled", true); - } - - if (ParentalControls.parentalControlsEnabled) { - let isBlockListEnabled = ParentalControls.isAllowed(ParentalControls.BLOCK_LIST); - Services.prefs.setBoolPref("browser.safebrowsing.forbiddenURIs.enabled", isBlockListEnabled); - Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", !isBlockListEnabled); - - let isTelemetryEnabled = ParentalControls.isAllowed(ParentalControls.TELEMETRY); - Services.prefs.setBoolPref("toolkit.telemetry.enabled", isTelemetryEnabled); - - let isHealthReportEnabled = ParentalControls.isAllowed(ParentalControls.HEALTH_REPORT); - SharedPreferences.forApp().setBoolPref("android.not_a_preference.healthreport.uploadEnabled", isHealthReportEnabled); - } - - let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); - if (sysInfo.get("version") < 16) { - let defaults = Services.prefs.getDefaultBranch(null); - defaults.setBoolPref("media.autoplay.enabled", false); - } - - InitLater(() => { - // The order that context menu items are added is important - // Make sure the "Open in App" context menu item appears at the bottom of the list - this.initContextMenu(); - ExternalApps.init(); - }, NativeWindow, "contextmenus"); - - if (AppConstants.ACCESSIBILITY) { - InitLater(() => AccessFu.attach(window), window, "AccessFu"); - } - - // Don't delay loading content.js because when we restore reader mode tabs, - // we require the reader mode scripts in content.js right away. - let mm = window.getGroupMessageManager("browsers"); - mm.loadFrameScript("chrome://browser/content/content.js", true); - - // Notify Java that Gecko has loaded. - Messaging.sendRequest({ type: "Gecko:Ready" }); - - this.deck.addEventListener("DOMContentLoaded", function BrowserApp_delayedStartup() { - BrowserApp.deck.removeEventListener("DOMContentLoaded", BrowserApp_delayedStartup, false); - - InitLater(() => Cu.import("resource://gre/modules/NotificationDB.jsm")); - InitLater(() => Cu.import("resource://gre/modules/PresentationDeviceInfoManager.jsm")); - - InitLater(() => Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "")); - InitLater(() => Messaging.sendRequest({ type: "Gecko:DelayedStartup" })); - - if (!AppConstants.RELEASE_OR_BETA) { - InitLater(() => WebcompatReporter.init()); - } - - // Collect telemetry data. - // We do this at startup because we want to move away from "gather-telemetry" (bug 1127907) - InitLater(() => { - Telemetry.addData("FENNEC_TRACKING_PROTECTION_STATE", parseInt(BrowserApp.getTrackingProtectionState())); - Telemetry.addData("ZOOMED_VIEW_ENABLED", Services.prefs.getBoolPref("ui.zoomedview.enabled")); - }); - - InitLater(() => LightWeightThemeWebInstaller.init()); - InitLater(() => SpatialNavigation.init(BrowserApp.deck, null), window, "SpatialNavigation"); - InitLater(() => CastingApps.init(), window, "CastingApps"); - InitLater(() => Services.search.init(), Services, "search"); - InitLater(() => DownloadNotifications.init(), window, "DownloadNotifications"); - -#ifdef MOZ_SAFE_BROWSING - // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. - InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing"); -#endif - - InitLater(() => Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager)); - InitLater(() => LoginManagerParent.init(), window, "LoginManagerParent"); - - }, false); - - // Pass caret StateChanged events to ActionBarHandler. - window.addEventListener("mozcaretstatechanged", e => { - ActionBarHandler.caretStateChangedHandler(e); - }, /* useCapture = */ true, /* wantsUntrusted = */ false); - }, - - get _startupStatus() { - delete this._startupStatus; - - let savedMilestone = null; - try { - savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone"); - } catch (e) { - } - let ourMilestone = AppConstants.MOZ_APP_VERSION; - this._startupStatus = ""; - if (ourMilestone != savedMilestone) { - Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourMilestone); - this._startupStatus = savedMilestone ? "upgrade" : "new"; - } - - return this._startupStatus; - }, - - /** - * Pass this a locale string, such as "fr" or "es_ES". - */ - setLocale: function (locale) { - console.log("browser.js: requesting locale set: " + locale); - Messaging.sendRequest({ type: "Locale:Set", locale: locale }); - }, - - initContextMenu: function () { - // We pass a thunk in place of a raw label string. This allows the - // context menu to automatically accommodate locale changes without - // having to be rebuilt. - let stringGetter = name => () => Strings.browser.GetStringFromName(name); - - // TODO: These should eventually move into more appropriate classes - NativeWindow.contextmenus.add(stringGetter("contextmenu.openInNewTab"), - NativeWindow.contextmenus.linkOpenableNonPrivateContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_new_tab"); - UITelemetry.addEvent("loadurl.1", "contextmenu", null); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal); - let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id }); - - let newtabStrings = Strings.browser.GetStringFromName("newtabpopup.opened"); - let label = PluralForm.get(1, newtabStrings).replace("#1", 1); - let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch"); - - Snackbars.show(label, Snackbars.LENGTH_LONG, { - action: { - label: buttonLabel, - callback: () => { BrowserApp.selectTab(tab); }, - } - }); - }); - - let showOpenInPrivateTab = true; - if ("@mozilla.org/parental-controls-service;1" in Cc) { - let pc = Cc["@mozilla.org/parental-controls-service;1"].createInstance(Ci.nsIParentalControlsService); - showOpenInPrivateTab = pc.isAllowed(Ci.nsIParentalControlsService.PRIVATE_BROWSING); - } - - if (showOpenInPrivateTab) { - NativeWindow.contextmenus.add(stringGetter("contextmenu.openInPrivateTab"), - NativeWindow.contextmenus.linkOpenableContext, - function (aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_new_tab"); - UITelemetry.addEvent("loadurl.1", "contextmenu", null); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal); - let tab = BrowserApp.addTab(url, {selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true}); - - let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened"); - let label = PluralForm.get(1, newtabStrings).replace("#1", 1); - let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch"); - Snackbars.show(label, Snackbars.LENGTH_LONG, { - action: { - label: buttonLabel, - callback: () => { BrowserApp.selectTab(tab); }, - } - }); - }); - } - - NativeWindow.contextmenus.add(stringGetter("contextmenu.copyLink"), - NativeWindow.contextmenus.linkCopyableContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_link"); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - NativeWindow.contextmenus._copyStringToDefaultClipboard(url); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.copyEmailAddress"), - NativeWindow.contextmenus.emailLinkContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_email"); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - let emailAddr = NativeWindow.contextmenus._stripScheme(url); - NativeWindow.contextmenus._copyStringToDefaultClipboard(emailAddr); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.copyPhoneNumber"), - NativeWindow.contextmenus.phoneNumberLinkContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_phone"); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - let phoneNumber = NativeWindow.contextmenus._stripScheme(url); - NativeWindow.contextmenus._copyStringToDefaultClipboard(phoneNumber); - }); - - NativeWindow.contextmenus.add({ - label: stringGetter("contextmenu.shareLink"), - order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, // Show above HTML5 menu items - selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.linkShareableContext), - showAsActions: function(aElement) { - return { - title: aElement.textContent.trim() || aElement.title.trim(), - uri: NativeWindow.contextmenus._getLinkURL(aElement), - }; - }, - icon: "drawable://ic_menu_share", - callback: function(aTarget) { - // share.1 telemetry is handled in Java via PromptList - UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_link"); - } - }); - - NativeWindow.contextmenus.add({ - label: stringGetter("contextmenu.shareEmailAddress"), - order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, - selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.emailLinkContext), - showAsActions: function(aElement) { - let url = NativeWindow.contextmenus._getLinkURL(aElement); - let emailAddr = NativeWindow.contextmenus._stripScheme(url); - let title = aElement.textContent || aElement.title; - return { - title: title, - uri: emailAddr, - }; - }, - icon: "drawable://ic_menu_share", - callback: function(aTarget) { - // share.1 telemetry is handled in Java via PromptList - UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_email"); - } - }); - - NativeWindow.contextmenus.add({ - label: stringGetter("contextmenu.sharePhoneNumber"), - order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, - selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.phoneNumberLinkContext), - showAsActions: function(aElement) { - let url = NativeWindow.contextmenus._getLinkURL(aElement); - let phoneNumber = NativeWindow.contextmenus._stripScheme(url); - let title = aElement.textContent || aElement.title; - return { - title: title, - uri: phoneNumber, - }; - }, - icon: "drawable://ic_menu_share", - callback: function(aTarget) { - // share.1 telemetry is handled in Java via PromptList - UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_phone"); - } - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.addToContacts"), - NativeWindow.contextmenus._disableRestricted("ADD_CONTACT", NativeWindow.contextmenus.emailLinkContext), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_email"); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - Messaging.sendRequest({ - type: "Contact:Add", - email: url - }); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.addToContacts"), - NativeWindow.contextmenus._disableRestricted("ADD_CONTACT", NativeWindow.contextmenus.phoneNumberLinkContext), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_phone"); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - Messaging.sendRequest({ - type: "Contact:Add", - phone: url - }); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.bookmarkLink"), - NativeWindow.contextmenus._disableRestricted("BOOKMARK", NativeWindow.contextmenus.linkBookmarkableContext), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_bookmark"); - UITelemetry.addEvent("save.1", "contextmenu", null, "bookmark"); - - let url = NativeWindow.contextmenus._getLinkURL(aTarget); - let title = aTarget.textContent || aTarget.title || url; - Messaging.sendRequest({ - type: "Bookmark:Insert", - url: url, - title: title - }); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.playMedia"), - NativeWindow.contextmenus.mediaContext("media-paused"), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_play"); - aTarget.play(); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.pauseMedia"), - NativeWindow.contextmenus.mediaContext("media-playing"), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_pause"); - aTarget.pause(); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.showControls2"), - NativeWindow.contextmenus.mediaContext("media-hidingcontrols"), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_controls_media"); - aTarget.setAttribute("controls", true); - }); - - NativeWindow.contextmenus.add({ - label: stringGetter("contextmenu.shareMedia"), - order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, - selector: NativeWindow.contextmenus._disableRestricted( - "SHARE", NativeWindow.contextmenus.videoContext()), - showAsActions: function(aElement) { - let url = (aElement.currentSrc || aElement.src); - let title = aElement.textContent || aElement.title; - return { - title: title, - uri: url, - type: "video/*", - }; - }, - icon: "drawable://ic_menu_share", - callback: function(aTarget) { - // share.1 telemetry is handled in Java via PromptList - UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_media"); - } - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.fullScreen"), - NativeWindow.contextmenus.videoContext("not-fullscreen"), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_fullscreen"); - aTarget.requestFullscreen(); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.mute"), - NativeWindow.contextmenus.mediaContext("media-unmuted"), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_mute"); - aTarget.muted = true; - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.unmute"), - NativeWindow.contextmenus.mediaContext("media-muted"), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_unmute"); - aTarget.muted = false; - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.viewImage"), - NativeWindow.contextmenus.imageLocationCopyableContext, - function(aTarget) { - let url = aTarget.src; - ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal, - Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); - - UITelemetry.addEvent("action.1", "contextmenu", null, "web_view_image"); - UITelemetry.addEvent("loadurl.1", "contextmenu", null); - BrowserApp.selectedBrowser.loadURI(url); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.copyImageLocation"), - NativeWindow.contextmenus.imageLocationCopyableContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_image"); - - let url = aTarget.src; - NativeWindow.contextmenus._copyStringToDefaultClipboard(url); - }); - - NativeWindow.contextmenus.add({ - label: stringGetter("contextmenu.shareImage"), - selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.imageShareableContext), - order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, // Show above HTML5 menu items - showAsActions: function(aTarget) { - let doc = aTarget.ownerDocument; - let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) - .getImgCacheForDocument(doc); - let props = imageCache.findEntryProperties(aTarget.currentURI, doc); - let src = aTarget.src; - return { - title: src, - uri: src, - type: "image/*", - }; - }, - icon: "drawable://ic_menu_share", - menu: true, - callback: function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_image"); - } - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.saveImage"), - NativeWindow.contextmenus.imageSaveableContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_image"); - UITelemetry.addEvent("save.1", "contextmenu", null, "image"); - - RuntimePermissions.waitForPermissions(RuntimePermissions.WRITE_EXTERNAL_STORAGE).then(function(permissionGranted) { - if (!permissionGranted) { - return; - } - - ContentAreaUtils.saveImageURL(aTarget.currentURI.spec, null, "SaveImageTitle", - false, true, aTarget.ownerDocument.documentURIObject, - aTarget.ownerDocument); - }); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.setImageAs"), - NativeWindow.contextmenus._disableRestricted("SET_IMAGE", NativeWindow.contextmenus.imageSaveableContext), - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_background_image"); - - let src = aTarget.src; - Messaging.sendRequest({ - type: "Image:SetAs", - url: src - }); - }); - - NativeWindow.contextmenus.add( - function(aTarget) { - if (aTarget instanceof HTMLVideoElement) { - // If a video element is zero width or height, its essentially - // an HTMLAudioElement. - if (aTarget.videoWidth == 0 || aTarget.videoHeight == 0 ) - return Strings.browser.GetStringFromName("contextmenu.saveAudio"); - return Strings.browser.GetStringFromName("contextmenu.saveVideo"); - } else if (aTarget instanceof HTMLAudioElement) { - return Strings.browser.GetStringFromName("contextmenu.saveAudio"); - } - return Strings.browser.GetStringFromName("contextmenu.saveVideo"); - }, NativeWindow.contextmenus.mediaSaveableContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_media"); - UITelemetry.addEvent("save.1", "contextmenu", null, "media"); - - let url = aTarget.currentSrc || aTarget.src; - let filePickerTitleKey = (aTarget instanceof HTMLVideoElement && - (aTarget.videoWidth != 0 && aTarget.videoHeight != 0)) - ? "SaveVideoTitle" : "SaveAudioTitle"; - // Skipped trying to pull MIME type out of cache for now - ContentAreaUtils.internalSave(url, null, null, null, null, false, - filePickerTitleKey, null, aTarget.ownerDocument.documentURIObject, - aTarget.ownerDocument, true, null); - }); - - NativeWindow.contextmenus.add(stringGetter("contextmenu.showImage"), - NativeWindow.contextmenus.imageBlockingPolicyContext, - function(aTarget) { - UITelemetry.addEvent("action.1", "contextmenu", null, "web_show_image"); - aTarget.setAttribute("data-ctv-show", "true"); - aTarget.setAttribute("src", aTarget.getAttribute("data-ctv-src")); - - // Shows a snackbar to unblock all images if browser.image_blocking.enabled is enabled. - let blockedImgs = aTarget.ownerDocument.querySelectorAll("[data-ctv-src]"); - if (blockedImgs.length == 0) { - return; - } - let message = Strings.browser.GetStringFromName("imageblocking.downloadedImage"); - Snackbars.show(message, Snackbars.LENGTH_LONG, { - action: { - label: Strings.browser.GetStringFromName("imageblocking.showAllImages"), - callback: () => { - UITelemetry.addEvent("action.1", "toast", null, "web_show_all_image"); - for (let i = 0; i < blockedImgs.length; ++i) { - blockedImgs[i].setAttribute("data-ctv-show", "true"); - blockedImgs[i].setAttribute("src", blockedImgs[i].getAttribute("data-ctv-src")); - } - }, - } - }); - }); - }, - - onAppUpdated: function() { - // initialize the form history and passwords databases on upgrades - Services.obs.notifyObservers(null, "FormHistory:Init", ""); - Services.obs.notifyObservers(null, "Passwords:Init", ""); - - if (this._startupStatus === "upgrade") { - this._migrateUI(); - } - }, - - _migrateUI: function() { - const UI_VERSION = 3; - let currentUIVersion = 0; - try { - currentUIVersion = Services.prefs.getIntPref("browser.migration.version"); - } catch(ex) {} - if (currentUIVersion >= UI_VERSION) { - return; - } - - if (currentUIVersion < 1) { - // Migrate user-set "plugins.click_to_play" pref. See bug 884694. - // Because the default value is true, a user-set pref means that the pref was set to false. - if (Services.prefs.prefHasUserValue("plugins.click_to_play")) { - Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED); - Services.prefs.clearUserPref("plugins.click_to_play"); - } - - // Migrate the "privacy.donottrackheader.value" pref. See bug 1042135. - if (Services.prefs.prefHasUserValue("privacy.donottrackheader.value")) { - // Make sure the doNotTrack value conforms to the conversion from - // three-state to two-state. (This reverts a setting of "please track me" - // to the default "don't say anything"). - if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled") && - (Services.prefs.getIntPref("privacy.donottrackheader.value") != 1)) { - Services.prefs.clearUserPref("privacy.donottrackheader.enabled"); - } - - // This pref has been removed, so always clear it. - Services.prefs.clearUserPref("privacy.donottrackheader.value"); - } - - // Set the search activity default pref on app upgrade if it has not been set already. - if (!Services.prefs.prefHasUserValue("searchActivity.default.migrated")) { - Services.prefs.setBoolPref("searchActivity.default.migrated", true); - SearchEngines.migrateSearchActivityDefaultPref(); - } - - Reader.migrateCache().catch(e => Cu.reportError("Error migrating Reader cache: " + e)); - - // We removed this pref from user visible settings, so we should reset it. - // Power users can go into about:config to re-enable this if they choose. - if (Services.prefs.prefHasUserValue("nglayout.debug.paint_flashing")) { - Services.prefs.clearUserPref("nglayout.debug.paint_flashing"); - } - } - - if (currentUIVersion < 2) { - let name; - if (Services.prefs.prefHasUserValue("browser.search.defaultenginename")) { - name = Services.prefs.getCharPref("browser.search.defaultenginename"); - } - if (!name && Services.prefs.prefHasUserValue("browser.search.defaultenginename.US")) { - name = Services.prefs.getCharPref("browser.search.defaultenginename.US"); - } - if (name) { - Services.search.init(() => { - let engine = Services.search.getEngineByName(name); - if (engine) { - Services.search.defaultEngine = engine; - Services.obs.notifyObservers(null, "default-search-engine-migrated", ""); - } - }); - } - } - - if (currentUIVersion < 3) { - const kOldSafeBrowsingPref = "browser.safebrowsing.enabled"; - // Default value is set to true, a user pref means that the pref was - // set to false. - if (Services.prefs.prefHasUserValue(kOldSafeBrowsingPref) && - !Services.prefs.getBoolPref(kOldSafeBrowsingPref)) { - Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", - false); - // Should just remove support for the pref entirely, even if it's - // only in about:config - Services.prefs.clearUserPref(kOldSafeBrowsingPref); - } - } - - // Update the migration version. - Services.prefs.setIntPref("browser.migration.version", UI_VERSION); - }, - - // This function returns false during periods where the browser displayed document is - // different from the browser content document, so user actions and some kinds of viewport - // updates should be ignored. This period starts when we start loading a new page or - // switch tabs, and ends when the new browser content document has been drawn and handed - // off to the compositor. - isBrowserContentDocumentDisplayed: function() { - try { - if (!Services.androidBridge.isContentDocumentDisplayed(window)) - return false; - } catch (e) { - return false; - } - - let tab = this.selectedTab; - if (!tab) - return false; - return tab.contentDocumentIsDisplayed; - }, - - contentDocumentChanged: function() { - window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true; - Services.androidBridge.contentDocumentChanged(window); - }, - - get tabs() { - return this._tabs; - }, - - set selectedTab(aTab) { - if (this._selectedTab == aTab) - return; - - if (this._selectedTab) { - this._selectedTab.setActive(false); - } - - this._selectedTab = aTab; - if (!aTab) - return; - - aTab.setActive(true); - this.contentDocumentChanged(); - this.deck.selectedPanel = aTab.browser; - // Focus the browser so that things like selection will be styled correctly. - aTab.browser.focus(); - }, - - get selectedBrowser() { - if (this._selectedTab) - return this._selectedTab.browser; - return null; - }, - - getTabForId: function getTabForId(aId) { - let tabs = this._tabs; - for (let i=0; i < tabs.length; i++) { - if (tabs[i].id == aId) - return tabs[i]; - } - return null; - }, - - getTabForBrowser: function getTabForBrowser(aBrowser) { - let tabs = this._tabs; - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].browser == aBrowser) - return tabs[i]; - } - return null; - }, - - getTabForWindow: function getTabForWindow(aWindow) { - let tabs = this._tabs; - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].browser.contentWindow == aWindow) - return tabs[i]; - } - return null; - }, - - getBrowserForWindow: function getBrowserForWindow(aWindow) { - let tabs = this._tabs; - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].browser.contentWindow == aWindow) - return tabs[i].browser; - } - return null; - }, - - getBrowserForDocument: function getBrowserForDocument(aDocument) { - let tabs = this._tabs; - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].browser.contentDocument == aDocument) - return tabs[i].browser; - } - return null; - }, - - loadURI: function loadURI(aURI, aBrowser, aParams) { - aBrowser = aBrowser || this.selectedBrowser; - if (!aBrowser) - return; - - aParams = aParams || {}; - - let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - let postData = ("postData" in aParams && aParams.postData) ? aParams.postData : null; - let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null; - let charset = "charset" in aParams ? aParams.charset : null; - - let tab = this.getTabForBrowser(aBrowser); - if (tab) { - if ("userRequested" in aParams) tab.userRequested = aParams.userRequested; - tab.isSearch = ("isSearch" in aParams) ? aParams.isSearch : false; - } - - try { - aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData); - } catch(e) { - if (tab) { - let message = { - type: "Content:LoadError", - tabID: tab.id - }; - Messaging.sendRequest(message); - dump("Handled load error: " + e) - } - } - }, - - addTab: function addTab(aURI, aParams) { - aParams = aParams || {}; - - let newTab = new Tab(aURI, aParams); - - if (typeof aParams.tabIndex == "number") { - this._tabs.splice(aParams.tabIndex, 0, newTab); - } else { - this._tabs.push(newTab); - } - - let selected = "selected" in aParams ? aParams.selected : true; - if (selected) - this.selectedTab = newTab; - - let pinned = "pinned" in aParams ? aParams.pinned : false; - if (pinned) { - let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - ss.setTabValue(newTab, "appOrigin", aURI); - } - - let evt = document.createEvent("UIEvents"); - evt.initUIEvent("TabOpen", true, false, window, null); - newTab.browser.dispatchEvent(evt); - - return newTab; - }, - - // Use this method to close a tab from JS. This method sends a message - // to Java to close the tab in the Java UI (we'll get a Tab:Closed message - // back from Java when that happens). - closeTab: function closeTab(aTab) { - if (!aTab) { - Cu.reportError("Error trying to close tab (tab doesn't exist)"); - return; - } - - let message = { - type: "Tab:Close", - tabID: aTab.id - }; - Messaging.sendRequest(message); - }, - - // Calling this will update the state in BrowserApp after a tab has been - // closed in the Java UI. - _handleTabClosed: function _handleTabClosed(aTab, aShowUndoSnackbar) { - if (aTab == this.selectedTab) - this.selectedTab = null; - - let tabIndex = this._tabs.indexOf(aTab); - - let evt = document.createEvent("UIEvents"); - evt.initUIEvent("TabClose", true, false, window, tabIndex); - aTab.browser.dispatchEvent(evt); - - if (aShowUndoSnackbar) { - // Get a title for the undo close snackbar. Fall back to the URL if there is no title. - let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - let closedTabData = ss.getClosedTabs(window)[0]; - - let message; - let title = closedTabData.entries[closedTabData.index - 1].title; - let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(aTab.browser); - - if (isPrivate) { - message = Strings.browser.GetStringFromName("privateClosedMessage.message"); - } else if (title) { - message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1); - } else { - message = Strings.browser.GetStringFromName("undoCloseToast.messageDefault"); - } - - Snackbars.show(message, Snackbars.LENGTH_LONG, { - action: { - label: Strings.browser.GetStringFromName("undoCloseToast.action2"), - callback: function() { - UITelemetry.addEvent("undo.1", "toast", null, "closetab"); - ss.undoCloseTab(window, closedTabData); - } - } - }); - } - - aTab.destroy(); - this._tabs.splice(tabIndex, 1); - }, - - // Use this method to select a tab from JS. This method sends a message - // to Java to select the tab in the Java UI (we'll get a Tab:Selected message - // back from Java when that happens). - selectTab: function selectTab(aTab) { - if (!aTab) { - Cu.reportError("Error trying to select tab (tab doesn't exist)"); - return; - } - - // There's nothing to do if the tab is already selected - if (aTab == this.selectedTab) - return; - - let doc = this.selectedBrowser.contentDocument; - if (doc.fullscreenElement) { - // We'll finish the tab selection once the fullscreen transition has ended, - // remember the new tab for this. - this.fullscreenTransitionTab = aTab; - doc.exitFullscreen(); - } - - let message = { - type: "Tab:Select", - tabID: aTab.id - }; - Messaging.sendRequest(message); - }, - - /** - * Gets an open tab with the given URL. - * - * @param aURL URL to look for - * @param aOptions Options for the search. Currently supports: - ** @option startsWith a Boolean indicating whether to search for a tab who's url starts with the - * requested url. Useful if you want to ignore hash codes on the end of a url. For instance - * to have about:downloads match about:downloads#123. - * @return the tab with the given URL, or null if no such tab exists - */ - getTabWithURL: function getTabWithURL(aURL, aOptions) { - aOptions = aOptions || {}; - let uri = Services.io.newURI(aURL, null, null); - for (let i = 0; i < this._tabs.length; ++i) { - let tab = this._tabs[i]; - if (aOptions.startsWith) { - if (tab.browser.currentURI.spec.startsWith(aURL)) { - return tab; - } - } else { - if (tab.browser.currentURI.equals(uri)) { - return tab; - } - } - } - return null; - }, - - /** - * If a tab with the given URL already exists, that tab is selected. - * Otherwise, a new tab is opened with the given URL. - * - * @param aURL URL to open - * @param aParam Options used if a tab is created - * @param aFlags Options for the search. Currently supports: - ** @option startsWith a Boolean indicating whether to search for a tab who's url starts with the - * requested url. Useful if you want to ignore hash codes on the end of a url. For instance - * to have about:downloads match about:downloads#123. - */ - selectOrAddTab: function selectOrAddTab(aURL, aParams, aFlags) { - let tab = this.getTabWithURL(aURL, aFlags); - if (tab == null) { - tab = this.addTab(aURL, aParams); - } else { - this.selectTab(tab); - } - - return tab; - }, - - // This method updates the state in BrowserApp after a tab has been selected - // in the Java UI. - _handleTabSelected: function _handleTabSelected(aTab) { - if (this.fullscreenTransitionTab) { - // Defer updating to "fullscreenchange" if tab selection happened during - // a fullscreen transition. - return; - } - this.selectedTab = aTab; - - let evt = document.createEvent("UIEvents"); - evt.initUIEvent("TabSelect", true, false, window, null); - aTab.browser.dispatchEvent(evt); - }, - - quit: function quit(aClear = { sanitize: {}, dontSaveSession: false }) { - // Notify all windows that an application quit has been requested. - let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); - Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); - - // Quit aborted. - if (cancelQuit.data) { - return; - } - - Services.obs.notifyObservers(null, "quit-application-proceeding", null); - - // Tell session store to forget about this window - if (aClear.dontSaveSession) { - let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - ss.removeWindow(window); - } - - BrowserApp.sanitize(aClear.sanitize, function() { - let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); - appStartup.quit(Ci.nsIAppStartup.eForceQuit); - }, true); - }, - - saveAsPDF: function saveAsPDF(aBrowser) { - RuntimePermissions.waitForPermissions(RuntimePermissions.WRITE_EXTERNAL_STORAGE).then(function(permissionGranted) { - if (!permissionGranted) { - return; - } - - Task.spawn(function* () { - let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null); - fileName = fileName.trim() + ".pdf"; - - let downloadsDir = yield Downloads.getPreferredDownloadsDirectory(); - let file = OS.Path.join(downloadsDir, fileName); - - // Force this to have a unique name. - let openedFile = yield OS.File.openUnique(file, { humanReadable: true }); - file = openedFile.path; - yield openedFile.file.close(); - - let download = yield Downloads.createDownload({ - source: aBrowser.contentWindow, - target: file, - saver: "pdf", - startTime: Date.now(), - }); - - let list = yield Downloads.getList(download.source.isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC) - yield list.add(download); - yield download.start(); - }); - }); - }, - - // These values come from pref_tracking_protection_entries in arrays.xml. - PREF_TRACKING_PROTECTION_ENABLED: "2", - PREF_TRACKING_PROTECTION_ENABLED_PB: "1", - PREF_TRACKING_PROTECTION_DISABLED: "0", - - /** - * Returns the current state of the tracking protection pref. - * (0 = Disabled, 1 = Enabled in PB, 2 = Enabled) - */ - getTrackingProtectionState: function() { - if (Services.prefs.getBoolPref("privacy.trackingprotection.enabled")) { - return this.PREF_TRACKING_PROTECTION_ENABLED; - } - if (Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled")) { - return this.PREF_TRACKING_PROTECTION_ENABLED_PB; - } - return this.PREF_TRACKING_PROTECTION_DISABLED; - }, - - sanitize: function (aItems, callback, aShutdown) { - let success = true; - var promises = []; - - for (let key in aItems) { - if (!aItems[key]) - continue; - - key = key.replace("private.data.", ""); - - switch (key) { - case "cookies_sessions": - promises.push(Sanitizer.clearItem("cookies")); - promises.push(Sanitizer.clearItem("sessions")); - break; - default: - promises.push(Sanitizer.clearItem(key)); - } - } - - Promise.all(promises).then(function() { - Messaging.sendRequest({ - type: "Sanitize:Finished", - success: true, - shutdown: aShutdown === true - }); - - if (callback) { - callback(); - } - }).catch(function(err) { - Messaging.sendRequest({ - type: "Sanitize:Finished", - error: err, - success: false, - shutdown: aShutdown === true - }); - - if (callback) { - callback(); - } - }) - }, - - getFocusedInput: function(aBrowser, aOnlyInputElements = false) { - if (!aBrowser) - return null; - - let doc = aBrowser.contentDocument; - if (!doc) - return null; - - let focused = doc.activeElement; - while (focused instanceof HTMLFrameElement || focused instanceof HTMLIFrameElement) { - doc = focused.contentDocument; - focused = doc.activeElement; - } - - if (focused instanceof HTMLInputElement && - (focused.mozIsTextField(false) || focused.type === "number")) { - return focused; - } - - if (aOnlyInputElements) - return null; - - if (focused && (focused instanceof HTMLTextAreaElement || focused.isContentEditable)) { - - if (focused instanceof HTMLBodyElement) { - // we are putting focus into a contentEditable frame. scroll the frame into - // view instead of the contentEditable document contained within, because that - // results in a better user experience - focused = focused.ownerDocument.defaultView.frameElement; - } - return focused; - } - return null; - }, - - scrollToFocusedInput: function(aBrowser, aAllowZoom = true) { - let formHelperMode = Services.prefs.getIntPref("formhelper.mode"); - if (formHelperMode == kFormHelperModeDisabled) - return; - - let dwu = aBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - if (!dwu) { - return; - } - - let apzFlushDone = function() { - Services.obs.removeObserver(apzFlushDone, "apz-repaints-flushed", false); - dwu.zoomToFocusedInput(); - }; - - let paintDone = function() { - window.removeEventListener("MozAfterPaint", paintDone, false); - if (dwu.flushApzRepaints()) { - Services.obs.addObserver(apzFlushDone, "apz-repaints-flushed", false); - } else { - apzFlushDone(); - } - }; - - let gotResizeWindow = false; - let resizeWindow = function(e) { - gotResizeWindow = true; - aBrowser.contentWindow.removeEventListener("resize", resizeWindow, false); - if (dwu.isMozAfterPaintPending) { - window.addEventListener("MozAfterPaint", paintDone, false); - } else { - paintDone(); - } - } - - aBrowser.contentWindow.addEventListener("resize", resizeWindow, false); - - // The "resize" event sometimes fails to fire, so set a timer to catch that case - // and unregister the event listener. See Bug 1253469 - setTimeout(function(e) { - if (!gotResizeWindow) { - aBrowser.contentWindow.removeEventListener("resize", resizeWindow, false); - dwu.zoomToFocusedInput(); - } - }, 500); - }, - - getUALocalePref: function () { - try { - return Services.prefs.getComplexValue("general.useragent.locale", Ci.nsIPrefLocalizedString).data; - } catch (e) { - try { - return Services.prefs.getCharPref("general.useragent.locale"); - } catch (ee) { - return undefined; - } - } - }, - - getOSLocalePref: function () { - try { - return Services.prefs.getCharPref("intl.locale.os"); - } catch (e) { - return undefined; - } - }, - - setLocalizedPref: function (pref, value) { - let pls = Cc["@mozilla.org/pref-localizedstring;1"] - .createInstance(Ci.nsIPrefLocalizedString); - pls.data = value; - Services.prefs.setComplexValue(pref, Ci.nsIPrefLocalizedString, pls); - }, - - observe: function(aSubject, aTopic, aData) { - let browser = this.selectedBrowser; - - switch (aTopic) { - - case "Session:Back": - browser.goBack(); - break; - - case "Session:Forward": - browser.goForward(); - break; - - case "Session:Navigate": - let index = JSON.parse(aData); - let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation); - let historySize = webNav.sessionHistory.count; - - if (index < 0) { - index = 0; - Log.e("Browser", "Negative index truncated to zero"); - } else if (index >= historySize) { - Log.e("Browser", "Incorrect index " + index + " truncated to " + historySize - 1); - index = historySize - 1; - } - - browser.gotoIndex(index); - break; - - case "Session:Reload": { - let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - - // Check to see if this is a message to enable/disable mixed content blocking. - if (aData) { - let data = JSON.parse(aData); - - if (data.bypassCache) { - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE | - Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY; - } - - if (data.contentType === "tracking") { - // Convert document URI into the format used by - // nsChannelClassifier::ShouldEnableTrackingProtection - // (any scheme turned into https is correct) - let normalizedUrl = Services.io.newURI("https://" + browser.currentURI.hostPort, null, null); - if (data.allowContent) { - // Add the current host in the 'trackingprotection' consumer of - // the permission manager using a normalized URI. This effectively - // places this host on the tracking protection white list. - if (PrivateBrowsingUtils.isBrowserPrivate(browser)) { - PrivateBrowsingUtils.addToTrackingAllowlist(normalizedUrl); - } else { - Services.perms.add(normalizedUrl, "trackingprotection", Services.perms.ALLOW_ACTION); - Telemetry.addData("TRACKING_PROTECTION_EVENTS", 1); - } - } else { - // Remove the current host from the 'trackingprotection' consumer - // of the permission manager. This effectively removes this host - // from the tracking protection white list (any list actually). - if (PrivateBrowsingUtils.isBrowserPrivate(browser)) { - PrivateBrowsingUtils.removeFromTrackingAllowlist(normalizedUrl); - } else { - Services.perms.remove(normalizedUrl, "trackingprotection"); - Telemetry.addData("TRACKING_PROTECTION_EVENTS", 2); - } - } - } - } - - // Try to use the session history to reload so that framesets are - // handled properly. If the window has no session history, fall back - // to using the web navigation's reload method. - let webNav = browser.webNavigation; - try { - let sh = webNav.sessionHistory; - if (sh) - webNav = sh.QueryInterface(Ci.nsIWebNavigation); - } catch (e) {} - webNav.reload(flags); - break; - } - - case "Session:Stop": - browser.stop(); - break; - - case "Tab:Load": { - let data = JSON.parse(aData); - let url = data.url; - let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP - | Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; - - // Pass LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL to prevent any loads from - // inheriting the currently loaded document's principal. - if (data.userEntered) { - flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; - } - - let delayLoad = ("delayLoad" in data) ? data.delayLoad : false; - let params = { - selected: ("selected" in data) ? data.selected : !delayLoad, - parentId: ("parentId" in data) ? data.parentId : -1, - flags: flags, - tabID: data.tabID, - isPrivate: (data.isPrivate === true), - pinned: (data.pinned === true), - delayLoad: (delayLoad === true), - desktopMode: (data.desktopMode === true) - }; - - params.userRequested = url; - - if (data.engine) { - let engine = Services.search.getEngineByName(data.engine); - if (engine) { - let submission = engine.getSubmission(url); - url = submission.uri.spec; - params.postData = submission.postData; - params.isSearch = true; - } - } - - if (data.newTab) { - this.addTab(url, params); - } else { - if (data.tabId) { - // Use a specific browser instead of the selected browser, if it exists - let specificBrowser = this.getTabForId(data.tabId).browser; - if (specificBrowser) - browser = specificBrowser; - } - this.loadURI(url, browser, params); - } - break; - } - - case "Tab:Selected": - this._handleTabSelected(this.getTabForId(parseInt(aData))); - break; - - case "Tab:Closed": { - let data = JSON.parse(aData); - this._handleTabClosed(this.getTabForId(data.tabId), data.showUndoToast); - break; - } - - case "keyword-search": - // This event refers to a search via the URL bar, not a bookmarks - // keyword search. Note that this code assumes that the user can only - // perform a keyword search on the selected tab. - this.selectedTab.isSearch = true; - - // Don't store queries in private browsing mode. - let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.selectedTab.browser); - let query = isPrivate ? "" : aData; - - let engine = aSubject.QueryInterface(Ci.nsISearchEngine); - Messaging.sendRequest({ - type: "Search:Keyword", - identifier: engine.identifier, - name: engine.name, - query: query - }); - break; - - case "Browser:Quit": - // Add-ons like QuitNow and CleanQuit provide aData as an empty-string (""). - // Pass undefined to invoke the methods default parms. - this.quit(aData ? JSON.parse(aData) : undefined); - break; - - case "SaveAs:PDF": - this.saveAsPDF(browser); - break; - - case "ScrollTo:FocusedInput": - // these messages come from a change in the viewable area and not user interaction - // we allow scrolling to the selected input, but not zooming the page - this.scrollToFocusedInput(browser, false); - break; - - case "Sanitize:ClearData": - this.sanitize(JSON.parse(aData)); - break; - - case "FullScreen:Exit": - browser.contentDocument.exitFullscreen(); - break; - - case "Passwords:Init": { - let storage = Cc["@mozilla.org/login-manager/storage/mozStorage;1"]. - getService(Ci.nsILoginManagerStorage); - storage.initialize(); - Services.obs.removeObserver(this, "Passwords:Init"); - break; - } - - case "FormHistory:Init": { - // Force creation/upgrade of formhistory.sqlite - FormHistory.count({}); - Services.obs.removeObserver(this, "FormHistory:Init"); - break; - } - - case "android-get-pref": { - // These pref names are not "real" pref names. They are used in the - // setting menu, and these are passed when initializing the setting - // menu. aSubject is a nsIWritableVariant to hold the pref value. - aSubject.QueryInterface(Ci.nsIWritableVariant); - - switch (aData) { - // The plugin pref is actually two separate prefs, so - // we need to handle it differently - case "plugin.enable": - aSubject.setAsAString(PluginHelper.getPluginPreference()); - break; - - // Handle master password - case "privacy.masterpassword.enabled": - aSubject.setAsBool(MasterPassword.enabled); - break; - - case "privacy.trackingprotection.state": { - aSubject.setAsAString(this.getTrackingProtectionState()); - break; - } - - // Crash reporter submit pref must be fetched from nsICrashReporter - // service. - case "datareporting.crashreporter.submitEnabled": - let crashReporterBuilt = "nsICrashReporter" in Ci && - Services.appinfo instanceof Ci.nsICrashReporter; - if (crashReporterBuilt) { - aSubject.setAsBool(Services.appinfo.submitReports); - } - break; - } - break; - } - - case "android-set-pref": { - // Pseudo-prefs. aSubject is an nsIWritableVariant that holds the pref - // value. Set to empty to signal the pref was handled. - aSubject.QueryInterface(Ci.nsIWritableVariant); - let value = aSubject.QueryInterface(Ci.nsIVariant); - - switch (aData) { - // The plugin pref is actually two separate prefs, so we need to - // handle it differently. - case "plugin.enable": - PluginHelper.setPluginPreference(value); - aSubject.setAsEmpty(); - break; - - // MasterPassword pref is not real, we just need take action and leave - case "privacy.masterpassword.enabled": - if (MasterPassword.enabled) { - MasterPassword.removePassword(value); - } else { - MasterPassword.setPassword(value); - } - aSubject.setAsEmpty(); - break; - - // "privacy.trackingprotection.state" is not a "real" pref name, but - // it's used in the setting menu. By default - // "privacy.trackingprotection.pbmode.enabled" is true, and - // "privacy.trackingprotection.enabled" is false. - case "privacy.trackingprotection.state": { - switch (value) { - // Tracking protection disabled. - case this.PREF_TRACKING_PROTECTION_DISABLED: - Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled", false); - Services.prefs.setBoolPref("privacy.trackingprotection.enabled", false); - break; - // Tracking protection only in private browsing, - case this.PREF_TRACKING_PROTECTION_ENABLED_PB: - Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled", true); - Services.prefs.setBoolPref("privacy.trackingprotection.enabled", false); - break; - // Tracking protection everywhere. - case this.PREF_TRACKING_PROTECTION_ENABLED: - Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled", true); - Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true); - break; - } - aSubject.setAsEmpty(); - break; - } - - // Crash reporter preference is in a service; set and return. - case "datareporting.crashreporter.submitEnabled": - let crashReporterBuilt = "nsICrashReporter" in Ci && - Services.appinfo instanceof Ci.nsICrashReporter; - if (crashReporterBuilt) { - Services.appinfo.submitReports = value; - aSubject.setAsEmpty(); - } - break; - } - break; - } - - case "sessionstore-state-purge-complete": - Messaging.sendRequest({ type: "Session:StatePurged" }); - break; - - case "gather-telemetry": - Messaging.sendRequest({ type: "Telemetry:Gather" }); - break; - - case "Locale:OS": - // We know the system locale. We use this for generating Accept-Language headers. - console.log("Locale:OS: " + aData); - let currentOSLocale = this.getOSLocalePref(); - if (currentOSLocale == aData) { - break; - } - - console.log("New OS locale."); - - // Ensure that this choice is immediately persisted, because - // Gecko won't be told again if it forgets. - Services.prefs.setCharPref("intl.locale.os", aData); - Services.prefs.savePrefFile(null); - - let appLocale = this.getUALocalePref(); - - this.computeAcceptLanguages(aData, appLocale); - break; - - case "Locale:Changed": - if (aData) { - // The value provided to Locale:Changed should be a BCP47 language tag - // understood by Gecko -- for example, "es-ES" or "de". - console.log("Locale:Changed: " + aData); - - // We always write a localized pref, even though sometimes the value is a char pref. - // (E.g., on desktop single-locale builds.) - this.setLocalizedPref("general.useragent.locale", aData); - } else { - // Resetting. - console.log("Switching to system locale."); - Services.prefs.clearUserPref("general.useragent.locale"); - } - - Services.prefs.setBoolPref("intl.locale.matchOS", !aData); - - // Ensure that this choice is immediately persisted, because - // Gecko won't be told again if it forgets. - Services.prefs.savePrefFile(null); - - // Blow away the string cache so that future lookups get the - // correct locale. - Strings.flush(); - - // Make sure we use the right Accept-Language header. - let osLocale; - try { - // This should never not be set at this point, but better safe than sorry. - osLocale = Services.prefs.getCharPref("intl.locale.os"); - } catch (e) { - } - - this.computeAcceptLanguages(osLocale, aData); - break; - - case "Fonts:Reload": - FontEnumerator.updateFontList(); - break; - - case "Vibration:Request": - if (aSubject instanceof Navigator) { - let navigator = aSubject; - let buttons = [ - { - label: Strings.browser.GetStringFromName("vibrationRequest.denyButton"), - callback: function() { - navigator.setVibrationPermission(false); - } - }, - { - label: Strings.browser.GetStringFromName("vibrationRequest.allowButton"), - callback: function() { - navigator.setVibrationPermission(true); - }, - positive: true - } - ]; - let message = Strings.browser.GetStringFromName("vibrationRequest.message"); - let options = {}; - NativeWindow.doorhanger.show(message, "vibration-request", buttons, - BrowserApp.selectedTab.id, options, "VIBRATION"); - } - break; - - default: - dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n'); - break; - - } - }, - - /** - * Set intl.accept_languages accordingly. - * - * After Bug 881510 this will also accept a real Accept-Language choice as - * input; all Accept-Language logic lives here. - * - * osLocale should never be null, but this method is safe regardless. - * appLocale may explicitly be null. - */ - computeAcceptLanguages(osLocale, appLocale) { - let defaultBranch = Services.prefs.getDefaultBranch(null); - let defaultAccept = defaultBranch.getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString).data; - console.log("Default intl.accept_languages = " + defaultAccept); - - // A guard for potential breakage. Bug 438031. - // This should not be necessary, because we're reading from the default branch, - // but better safe than sorry. - if (defaultAccept && defaultAccept.startsWith("chrome://")) { - defaultAccept = null; - } else { - // Ensure lowercase everywhere so we can compare. - defaultAccept = defaultAccept.toLowerCase(); - } - - if (appLocale) { - appLocale = appLocale.toLowerCase(); - } - - if (osLocale) { - osLocale = osLocale.toLowerCase(); - } - - // Eliminate values if they're present in the default. - let chosen; - if (defaultAccept) { - // intl.accept_languages is a comma-separated list, with no q-value params. Those - // are added when the header is generated. - chosen = defaultAccept.split(",") - .map(String.trim) - .filter((x) => (x != appLocale && x != osLocale)); - } else { - chosen = []; - } - - if (osLocale) { - chosen.unshift(osLocale); - } - - if (appLocale && appLocale != osLocale) { - chosen.unshift(appLocale); - } - - let result = chosen.join(","); - console.log("Setting intl.accept_languages to " + result); - this.setLocalizedPref("intl.accept_languages", result); - }, - - // nsIAndroidBrowserApp - get selectedTab() { - return this._selectedTab; - }, - - // nsIAndroidBrowserApp - getBrowserTab: function(tabId) { - return this.getTabForId(tabId); - }, - - getUITelemetryObserver: function() { - return UITelemetry; - }, - - // This method will return a list of history items and toIndex based on the action provided from the fromIndex to toIndex, - // optionally selecting selIndex (if fromIndex <= selIndex <= toIndex) - getHistory: function(data) { - let action = data.action; - let webNav = BrowserApp.getTabForId(data.tabId).window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation); - let historyIndex = webNav.sessionHistory.index; - let historySize = webNav.sessionHistory.count; - let canGoBack = webNav.canGoBack; - let canGoForward = webNav.canGoForward; - let listitems = []; - let fromIndex = 0; - let toIndex = historySize - 1; - let selIndex = historyIndex; - - if (action == "BACK" && canGoBack) { - fromIndex = Math.max(historyIndex - kMaxHistoryListSize, 0); - toIndex = historyIndex; - selIndex = historyIndex; - } else if (action == "FORWARD" && canGoForward) { - fromIndex = historyIndex; - toIndex = Math.min(historySize - 1, historyIndex + kMaxHistoryListSize); - selIndex = historyIndex; - } else if (action == "ALL" && (canGoBack || canGoForward)){ - fromIndex = historyIndex - kMaxHistoryListSize / 2; - toIndex = historyIndex + kMaxHistoryListSize / 2; - if (fromIndex < 0) { - toIndex -= fromIndex; - } - - if (toIndex > historySize - 1) { - fromIndex -= toIndex - (historySize - 1); - toIndex = historySize - 1; - } - - fromIndex = Math.max(fromIndex, 0); - selIndex = historyIndex; - } else { - // return empty list immediately. - return { - "historyItems": listitems, - "toIndex": toIndex - }; - } - - let browser = this.selectedBrowser; - let hist = browser.sessionHistory; - for (let i = toIndex; i >= fromIndex; i--) { - let entry = hist.getEntryAtIndex(i, false); - let item = { - title: entry.title || entry.URI.spec, - url: entry.URI.spec, - selected: (i == selIndex) - }; - listitems.push(item); - } - - return { - "historyItems": listitems, - "toIndex": toIndex - }; - }, -}; - -var NativeWindow = { - init: function() { - Services.obs.addObserver(this, "Menu:Clicked", false); - Services.obs.addObserver(this, "Doorhanger:Reply", false); - this.contextmenus.init(); - }, - - loadDex: function(zipFile, implClass) { - Messaging.sendRequest({ - type: "Dex:Load", - zipfile: zipFile, - impl: implClass || "Main" - }); - }, - - unloadDex: function(zipFile) { - Messaging.sendRequest({ - type: "Dex:Unload", - zipfile: zipFile - }); - }, - - menu: { - _callbacks: [], - _menuId: 1, - toolsMenuID: -1, - add: function() { - let options; - if (arguments.length == 1) { - options = arguments[0]; - } else if (arguments.length == 3) { - Log.w("Browser", "This menu addon API has been deprecated. Instead, use the options object API."); - options = { - name: arguments[0], - callback: arguments[2] - }; - } else { - throw "Incorrect number of parameters"; - } - - options.type = "Menu:Add"; - options.id = this._menuId; - - Messaging.sendRequest(options); - this._callbacks[this._menuId] = options.callback; - this._menuId++; - return this._menuId - 1; - }, - - remove: function(aId) { - Messaging.sendRequest({ type: "Menu:Remove", id: aId }); - }, - - update: function(aId, aOptions) { - if (!aOptions) - return; - - Messaging.sendRequest({ - type: "Menu:Update", - id: aId, - options: aOptions - }); - } - }, - - doorhanger: { - _callbacks: {}, - _callbacksId: 0, - _promptId: 0, - - /** - * @param aOptions - * An options JavaScript object holding additional properties for the - * notification. The following properties are currently supported: - * persistence: An integer. The notification will not automatically - * dismiss for this many page loads. If persistence is set - * to -1, the doorhanger will never automatically dismiss. - * persistWhileVisible: - * A boolean. If true, a visible notification will always - * persist across location changes. - * timeout: A time in milliseconds. The notification will not - * automatically dismiss before this time. - * - * checkbox: A string to appear next to a checkbox under the notification - * message. The button callback functions will be called with - * the checked state as an argument. - * - * actionText: An object that specifies a clickable string, a type of action, - * and a bundle blob for the consumer to create a click action. - * { text: <text>, - * type: <type>, - * bundle: <blob-object> } - * - * @param aCategory - * Doorhanger type to display (e.g., LOGIN) - */ - show: function(aMessage, aValue, aButtons, aTabID, aOptions, aCategory) { - if (aButtons == null) { - aButtons = []; - } - - if (aButtons.length > 2) { - console.log("Doorhanger can have a maximum of two buttons!"); - aButtons.length = 2; - } - - aButtons.forEach((function(aButton) { - this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId }; - aButton.callback = this._callbacksId; - this._callbacksId++; - }).bind(this)); - - this._promptId++; - let json = { - type: "Doorhanger:Add", - message: aMessage, - value: aValue, - buttons: aButtons, - // use the current tab if none is provided - tabID: aTabID || BrowserApp.selectedTab.id, - options: aOptions || {}, - category: aCategory - }; - Messaging.sendRequest(json); - }, - - hide: function(aValue, aTabID) { - Messaging.sendRequest({ - type: "Doorhanger:Remove", - value: aValue, - tabID: aTabID - }); - } - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "Menu:Clicked") { - if (this.menu._callbacks[aData]) - this.menu._callbacks[aData](); - } else if (aTopic == "Doorhanger:Reply") { - let data = JSON.parse(aData); - let reply_id = data["callback"]; - - if (this.doorhanger._callbacks[reply_id]) { - // Pass the value of the optional checkbox to the callback - let checked = data["checked"]; - this.doorhanger._callbacks[reply_id].cb(checked, data.inputs); - - let prompt = this.doorhanger._callbacks[reply_id].prompt; - for (let id in this.doorhanger._callbacks) { - if (this.doorhanger._callbacks[id].prompt == prompt) { - delete this.doorhanger._callbacks[id]; - } - } - } - } - }, - - contextmenus: { - items: {}, // a list of context menu items that we may show - DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items - - init: function() { - // Accessing "NativeWindow.contextmenus" initializes context menus if needed. - BrowserApp.deck.addEventListener( - "contextmenu", (e) => NativeWindow.contextmenus.show(e), false); - }, - - add: function() { - let args; - if (arguments.length == 1) { - args = arguments[0]; - } else if (arguments.length == 3) { - args = { - label : arguments[0], - selector: arguments[1], - callback: arguments[2] - }; - } else { - throw "Incorrect number of parameters"; - } - - if (!args.label) - throw "Menu items must have a name"; - - let cmItem = new ContextMenuItem(args); - this.items[cmItem.id] = cmItem; - return cmItem.id; - }, - - remove: function(aId) { - delete this.items[aId]; - }, - - // Although we do not use this ourselves anymore, add-ons may still - // need it as it has been documented, so we shouldn't remove it. - SelectorContext: function(aSelector) { - return { - matches: function(aElt) { - if (aElt.matches) - return aElt.matches(aSelector); - return false; - } - }; - }, - - linkOpenableNonPrivateContext: { - matches: function linkOpenableNonPrivateContextMatches(aElement) { - let doc = aElement.ownerDocument; - if (!doc || PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView)) { - return false; - } - - return NativeWindow.contextmenus.linkOpenableContext.matches(aElement); - } - }, - - linkOpenableContext: { - matches: function linkOpenableContextMatches(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri) { - let scheme = uri.scheme; - let dontOpen = /^(javascript|mailto|news|snews|tel)$/; - return (scheme && !dontOpen.test(scheme)); - } - return false; - } - }, - - linkCopyableContext: { - matches: function linkCopyableContextMatches(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri) { - let scheme = uri.scheme; - let dontCopy = /^(mailto|tel)$/; - return (scheme && !dontCopy.test(scheme)); - } - return false; - } - }, - - linkShareableContext: { - matches: function linkShareableContextMatches(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri) { - let scheme = uri.scheme; - let dontShare = /^(about|chrome|file|javascript|mailto|resource|tel)$/; - return (scheme && !dontShare.test(scheme)); - } - return false; - } - }, - - linkBookmarkableContext: { - matches: function linkBookmarkableContextMatches(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri) { - let scheme = uri.scheme; - let dontBookmark = /^(mailto|tel)$/; - return (scheme && !dontBookmark.test(scheme)); - } - return false; - } - }, - - emailLinkContext: { - matches: function emailLinkContextMatches(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri) - return uri.schemeIs("mailto"); - return false; - } - }, - - phoneNumberLinkContext: { - matches: function phoneNumberLinkContextMatches(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri) - return uri.schemeIs("tel"); - return false; - } - }, - - imageLocationCopyableContext: { - matches: function imageLinkCopyableContextMatches(aElement) { - if (aElement instanceof Ci.nsIDOMHTMLImageElement) { - // The image is blocked by Tap-to-load Images - if (aElement.hasAttribute("data-ctv-src") && !aElement.hasAttribute("data-ctv-show")) { - return false; - } - } - return (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI); - } - }, - - imageSaveableContext: { - matches: function imageSaveableContextMatches(aElement) { - if (aElement instanceof Ci.nsIDOMHTMLImageElement) { - // The image is blocked by Tap-to-load Images - if (aElement.hasAttribute("data-ctv-src") && !aElement.hasAttribute("data-ctv-show")) { - return false; - } - } - if (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI) { - // The image must be loaded to allow saving - let request = aElement.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); - return (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)); - } - return false; - } - }, - - imageShareableContext: { - matches: function imageShareableContextMatches(aElement) { - let imgSrc = ''; - if (aElement instanceof Ci.nsIDOMHTMLImageElement) { - imgSrc = aElement.src; - } else if (aElement instanceof Ci.nsIImageLoadingContent && - aElement.currentURI && - aElement.currentURI.spec) { - imgSrc = aElement.currentURI.spec; - } - - // In order to share an image, we need to pass the image src over IPC via an Intent (in - // `ApplicationPackageManager.queryIntentActivities`). However, the transaction has a 1MB limit - // (shared by all transactions in progress) - otherwise we crash! (bug 1243305) - // https://developer.android.com/reference/android/os/TransactionTooLargeException.html - // - // The transaction limit is 1MB and we arbitrarily choose to cap this transaction at 1/4 of that = 250,000 bytes. - // In Java, a UTF-8 character is 1-4 bytes so, 250,000 bytes / 4 bytes/char = 62,500 char - let MAX_IMG_SRC_LEN = 62500; - let isTooLong = imgSrc.length >= MAX_IMG_SRC_LEN; - return !isTooLong && this.NativeWindow.contextmenus.imageSaveableContext.matches(aElement); - }.bind(this) - }, - - mediaSaveableContext: { - matches: function mediaSaveableContextMatches(aElement) { - return (aElement instanceof HTMLVideoElement || - aElement instanceof HTMLAudioElement); - } - }, - - imageBlockingPolicyContext: { - matches: function imageBlockingPolicyContextMatches(aElement) { - if (aElement instanceof Ci.nsIDOMHTMLImageElement && aElement.getAttribute("data-ctv-src")) { - // Only show the menuitem if we are blocking the image - if (aElement.getAttribute("data-ctv-show") == "true") { - return false; - } - return true; - } - return false; - } - }, - - mediaContext: function(aMode) { - return { - matches: function(aElt) { - if (aElt instanceof Ci.nsIDOMHTMLMediaElement) { - let hasError = aElt.error != null || aElt.networkState == aElt.NETWORK_NO_SOURCE; - if (hasError) - return false; - - let paused = aElt.paused || aElt.ended; - if (paused && aMode == "media-paused") - return true; - if (!paused && aMode == "media-playing") - return true; - let controls = aElt.controls; - if (!controls && aMode == "media-hidingcontrols") - return true; - - let muted = aElt.muted; - if (muted && aMode == "media-muted") - return true; - else if (!muted && aMode == "media-unmuted") - return true; - } - return false; - } - }; - }, - - videoContext: function(aMode) { - return { - matches: function(aElt) { - if (aElt instanceof HTMLVideoElement) { - if (!aMode) { - return true; - } - var isFullscreen = aElt.ownerDocument.fullscreenElement == aElt; - if (aMode == "not-fullscreen") { - return !isFullscreen; - } - if (aMode == "fullscreen") { - return isFullscreen; - } - } - return false; - } - }; - }, - - /* Holds a WeakRef to the original target element this context menu was shown for. - * Most API's will have to walk up the tree from this node to find the correct element - * to act on - */ - get _target() { - if (this._targetRef) - return this._targetRef.get(); - return null; - }, - - set _target(aTarget) { - if (aTarget) - this._targetRef = Cu.getWeakReference(aTarget); - else this._targetRef = null; - }, - - get defaultContext() { - delete this.defaultContext; - return this.defaultContext = Strings.browser.GetStringFromName("browser.menu.context.default"); - }, - - /* Gets menuitems for an arbitrary node - * Parameters: - * element - The element to look at. If this element has a contextmenu attribute, the - * corresponding contextmenu will be used. - */ - _getHTMLContextMenuItemsForElement: function(element) { - let htmlMenu = element.contextMenu; - if (!htmlMenu) { - return []; - } - - htmlMenu.QueryInterface(Components.interfaces.nsIHTMLMenu); - htmlMenu.sendShowEvent(); - - return this._getHTMLContextMenuItemsForMenu(htmlMenu, element); - }, - - /* Add a menuitem for an HTML <menu> node - * Parameters: - * menu - The <menu> element to iterate through for menuitems - * target - The target element these context menu items are attached to - */ - _getHTMLContextMenuItemsForMenu: function(menu, target) { - let items = []; - for (let i = 0; i < menu.childNodes.length; i++) { - let elt = menu.childNodes[i]; - if (!elt.label) - continue; - - items.push(new HTMLContextMenuItem(elt, target)); - } - - return items; - }, - - // Searches the current list of menuitems to show for any that match this id - _findMenuItem: function(aId) { - if (!this.menus) { - return null; - } - - for (let context in this.menus) { - let menu = this.menus[context]; - for (let i = 0; i < menu.length; i++) { - if (menu[i].id === aId) { - return menu[i]; - } - } - } - return null; - }, - - // Returns true if there are any context menu items to show - _shouldShow: function() { - for (let context in this.menus) { - let menu = this.menus[context]; - if (menu.length > 0) { - return true; - } - } - return false; - }, - - /* Returns a label to be shown in a tabbed ui if there are multiple "contexts". For instance, if this - * is an image inside an <a> tag, we may have a "link" context and an "image" one. - */ - _getContextType: function(element) { - // For anchor nodes, we try to use the scheme to pick a string - if (element instanceof Ci.nsIDOMHTMLAnchorElement) { - let uri = this.makeURI(this._getLinkURL(element)); - try { - return Strings.browser.GetStringFromName("browser.menu.context." + uri.scheme); - } catch(ex) { } - } - - // Otherwise we try the nodeName - try { - return Strings.browser.GetStringFromName("browser.menu.context." + element.nodeName.toLowerCase()); - } catch(ex) { } - - // Fallback to the default - return this.defaultContext; - }, - - // Adds context menu items added through the add-on api - _getNativeContextMenuItems: function(element, x, y) { - let res = []; - for (let itemId of Object.keys(this.items)) { - let item = this.items[itemId]; - - if (!this._findMenuItem(item.id) && item.matches(element, x, y)) { - res.push(item); - } - } - - return res; - }, - - /* Checks if there are context menu items to show, and if it finds them - * sends a contextmenu event to content. We also send showing events to - * any html5 context menus we are about to show, and fire some local notifications - * for chrome consumers to do lazy menuitem construction - */ - show: function(event) { - // Android Long-press / contextmenu event provides clientX/Y data. This is not provided - // by mochitest: test_browserElement_inproc_ContextmenuEvents.html. - if (!event.clientX || !event.clientY) { - return; - } - - // If the event was already defaultPrevented by somebody (web content, or - // some other part of gecko), then don't do anything with it. - if (event.defaultPrevented) { - return; - } - - // Use the highlighted element for the context menu target. When accessibility is - // enabled, elements may not be highlighted so use the event target instead. - this._target = BrowserEventHandler._highlightElement || event.target; - if (!this._target) { - return; - } - - // Try to build a list of contextmenu items. If successful, actually show the - // native context menu by passing the list to Java. - this._buildMenu(event.clientX, event.clientY); - if (this._shouldShow()) { - BrowserEventHandler._cancelTapHighlight(); - - // Consume / preventDefault the event, and show the contextmenu. - event.preventDefault(); - this._innerShow(this._target, event.clientX, event.clientY); - this._target = null; - - return; - } - - // If no context-menu for long-press event, it may be meant to trigger text-selection. - this.menus = null; - Services.obs.notifyObservers( - {target: this._target, x: event.clientX, y: event.clientY}, "context-menu-not-shown", ""); - }, - - // Returns a title for a context menu. If no title attribute exists, will fall back to looking for a url - _getTitle: function(node) { - if (node.hasAttribute && node.hasAttribute("title")) { - return node.getAttribute("title"); - } - return this._getUrl(node); - }, - - // Returns a url associated with a node - _getUrl: function(node) { - if ((node instanceof Ci.nsIDOMHTMLAnchorElement && node.href) || - (node instanceof Ci.nsIDOMHTMLAreaElement && node.href)) { - return this._getLinkURL(node); - } else if (node instanceof Ci.nsIImageLoadingContent && node.currentURI) { - // The image is blocked by Tap-to-load Images - let originalURL = node.getAttribute("data-ctv-src"); - if (originalURL) { - return originalURL; - } - return node.currentURI.spec; - } else if (node instanceof Ci.nsIDOMHTMLMediaElement) { - let srcUrl = node.currentSrc || node.src; - // If URL prepended with blob or mediasource, we'll remove it. - return srcUrl.replace(/^(?:blob|mediasource):/, ''); - } - - return ""; - }, - - // Adds an array of menuitems to the current list of items to show, in the correct context - _addMenuItems: function(items, context) { - if (!this.menus[context]) { - this.menus[context] = []; - } - this.menus[context] = this.menus[context].concat(items); - }, - - /* Does the basic work of building a context menu to show. Will combine HTML and Native - * context menus items, as well as sorting menuitems into different menus based on context. - */ - _buildMenu: function(x, y) { - // now walk up the tree and for each node look for any context menu items that apply - let element = this._target; - - // this.menus holds a hashmap of "contexts" to menuitems associated with that context - // For instance, if the user taps an image inside a link, we'll have something like: - // { - // link: [ ContextMenuItem, ContextMenuItem ] - // image: [ ContextMenuItem, ContextMenuItem ] - // } - this.menus = {}; - - while (element) { - let context = this._getContextType(element); - - // First check for any html5 context menus that might exist... - var items = this._getHTMLContextMenuItemsForElement(element); - if (items.length > 0) { - this._addMenuItems(items, context); - } - - // then check for any context menu items registered in the ui. - items = this._getNativeContextMenuItems(element, x, y); - if (items.length > 0) { - this._addMenuItems(items, context); - } - - // walk up the tree and find more items to show - element = element.parentNode; - } - }, - - // Walks the DOM tree to find a title from a node - _findTitle: function(node) { - let title = ""; - while(node && !title) { - title = this._getTitle(node); - node = node.parentNode; - } - return title; - }, - - /* Reformats the list of menus to show into an object that can be sent to Prompt.jsm - * If there is one menu, will return a flat array of menuitems. If there are multiple - * menus, will return an array with appropriate tabs/items inside it. i.e. : - * [ - * { label: "link", items: [...] }, - * { label: "image", items: [...] } - * ] - */ - _reformatList: function(target) { - let contexts = Object.keys(this.menus); - - if (contexts.length === 1) { - // If there's only one context, we'll only show a single flat single select list - return this._reformatMenuItems(target, this.menus[contexts[0]]); - } - - // If there are multiple contexts, we'll only show a tabbed ui with multiple lists - return this._reformatListAsTabs(target, this.menus); - }, - - /* Reformats the list of menus to show into an object that can be sent to Prompt.jsm's - * addTabs method. i.e. : - * { link: [...], image: [...] } becomes - * [ { label: "link", items: [...] } ] - * - * Also reformats items and resolves any parmaeters that aren't known until display time - * (for instance Helper app menu items adjust their title to reflect what Helper App can be used for this link). - */ - _reformatListAsTabs: function(target, menus) { - let itemArray = []; - - // Sort the keys so that "link" is always first - let contexts = Object.keys(this.menus); - contexts.sort((context1, context2) => { - if (context1 === this.defaultContext) { - return -1; - } else if (context2 === this.defaultContext) { - return 1; - } - return 0; - }); - - contexts.forEach(context => { - itemArray.push({ - label: context, - items: this._reformatMenuItems(target, menus[context]) - }); - }); - - return itemArray; - }, - - /* Reformats an array of ContextMenuItems into an array that can be handled by Prompt.jsm. Also reformats items - * and resolves any parmaeters that aren't known until display time - * (for instance Helper app menu items adjust their title to reflect what Helper App can be used for this link). - */ - _reformatMenuItems: function(target, menuitems) { - let itemArray = []; - - for (let i = 0; i < menuitems.length; i++) { - let t = target; - while(t) { - if (menuitems[i].matches(t)) { - let val = menuitems[i].getValue(t); - - // hidden menu items will return null from getValue - if (val) { - itemArray.push(val); - break; - } - } - - t = t.parentNode; - } - } - - return itemArray; - }, - - // Called where we're finally ready to actually show the contextmenu. Sorts the items and shows a prompt. - _innerShow: function(target, x, y) { - Haptic.performSimpleAction(Haptic.LongPress); - - // spin through the tree looking for a title for this context menu - let title = this._findTitle(target); - - for (let context in this.menus) { - let menu = this.menus[context]; - menu.sort((a,b) => { - if (a.order === b.order) { - return 0; - } - return (a.order > b.order) ? 1 : -1; - }); - } - - let useTabs = Object.keys(this.menus).length > 1; - let prompt = new Prompt({ - window: target.ownerDocument.defaultView, - title: useTabs ? undefined : title - }); - - let items = this._reformatList(target); - if (useTabs) { - prompt.addTabs({ - id: "tabs", - items: items - }); - } else { - prompt.setSingleChoiceItems(items); - } - - prompt.show(this._promptDone.bind(this, target, x, y, items)); - }, - - // Called when the contextmenu prompt is closed - _promptDone: function(target, x, y, items, data) { - if (data.button == -1) { - // Prompt was cancelled, or an ActionView was used. - return; - } - - let selectedItemId; - if (data.tabs) { - let menu = items[data.tabs.tab]; - selectedItemId = menu.items[data.tabs.item].id; - } else { - selectedItemId = items[data.list[0]].id - } - - let selectedItem = this._findMenuItem(selectedItemId); - this.menus = null; - - if (!selectedItem || !selectedItem.matches || !selectedItem.callback) { - return; - } - - // for menuitems added using the native UI, pass the dom element that matched that item to the callback - while (target) { - if (selectedItem.matches(target, x, y)) { - selectedItem.callback(target, x, y); - break; - } - target = target.parentNode; - } - }, - - // XXX - These are stolen from Util.js, we should remove them if we bring it back - makeURLAbsolute: function makeURLAbsolute(base, url) { - // Note: makeURI() will throw if url is not a valid URI - return this.makeURI(url, null, this.makeURI(base)).spec; - }, - - makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) { - return Services.io.newURI(aURL, aOriginCharset, aBaseURI); - }, - - _getLink: function(aElement) { - if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && - ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) || - (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) || - aElement instanceof Ci.nsIDOMHTMLLinkElement || - aElement.getAttributeNS(kXLinkNamespace, "type") == "simple")) { - try { - let url = this._getLinkURL(aElement); - return Services.io.newURI(url, null, null); - } catch (e) {} - } - return null; - }, - - _disableRestricted: function _disableRestricted(restriction, selector) { - return { - matches: function _disableRestrictedMatches(aElement, aX, aY) { - if (!ParentalControls.isAllowed(ParentalControls[restriction])) { - return false; - } - - return selector.matches(aElement, aX, aY); - } - }; - }, - - _getLinkURL: function ch_getLinkURL(aLink) { - let href = aLink.href; - if (href) - return href; - - href = aLink.getAttribute("href") || - aLink.getAttributeNS(kXLinkNamespace, "href"); - if (!href || !href.match(/\S/)) { - // Without this we try to save as the current doc, - // for example, HTML case also throws if empty - throw "Empty href"; - } - - return this.makeURLAbsolute(aLink.baseURI, href); - }, - - _copyStringToDefaultClipboard: function(aString) { - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); - clipboard.copyString(aString); - Snackbars.show(Strings.browser.GetStringFromName("selectionHelper.textCopied"), Snackbars.LENGTH_LONG); - }, - - _stripScheme: function(aString) { - let index = aString.indexOf(":"); - return aString.slice(index + 1); - } - } -}; - -XPCOMUtils.defineLazyModuleGetter(this, "PageActions", - "resource://gre/modules/PageActions.jsm"); - -// These alias to the old, deprecated NativeWindow interfaces -[ - ["pageactions", "resource://gre/modules/PageActions.jsm", "PageActions"], - ["toast", "resource://gre/modules/Snackbars.jsm", "Snackbars"] -].forEach(item => { - let [name, script, exprt] = item; - - XPCOMUtils.defineLazyGetter(NativeWindow, name, () => { - var err = Strings.browser.formatStringFromName("nativeWindow.deprecated", ["NativeWindow." + name, script], 2); - Cu.reportError(err); - - let sandbox = {}; - Cu.import(script, sandbox); - return sandbox[exprt]; - }); -}); - -var LightWeightThemeWebInstaller = { - init: function sh_init() { - let temp = {}; - Cu.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp); - let theme = new temp.LightweightThemeConsumer(document); - BrowserApp.deck.addEventListener("InstallBrowserTheme", this, false, true); - BrowserApp.deck.addEventListener("PreviewBrowserTheme", this, false, true); - BrowserApp.deck.addEventListener("ResetBrowserThemePreview", this, false, true); - - if (ParentalControls.parentalControlsEnabled && - !this._manager.currentTheme && - ParentalControls.isAllowed(ParentalControls.DEFAULT_THEME)) { - // We are using the DEFAULT_THEME restriction to differentiate between restricted profiles & guest mode - Bug 1199596 - this._installParentalControlsTheme(); - } - }, - - handleEvent: function (event) { - switch (event.type) { - case "InstallBrowserTheme": - case "PreviewBrowserTheme": - case "ResetBrowserThemePreview": - // ignore requests from background tabs - if (event.target.ownerDocument.defaultView.top != content) - return; - } - - switch (event.type) { - case "InstallBrowserTheme": - this._installRequest(event); - break; - case "PreviewBrowserTheme": - this._preview(event); - break; - case "ResetBrowserThemePreview": - this._resetPreview(event); - break; - case "pagehide": - case "TabSelect": - this._resetPreview(); - break; - } - }, - - get _manager () { - let temp = {}; - Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp); - delete this._manager; - return this._manager = temp.LightweightThemeManager; - }, - - _installParentalControlsTheme: function() { - let mgr = this._manager; - let parentalControlsTheme = { - "headerURL": "resource://android/assets/parental_controls_theme.png", - "name": "Parental Controls Theme", - "id": "parental-controls-theme@mozilla.org" - }; - - mgr.addBuiltInTheme(parentalControlsTheme); - mgr.themeChanged(parentalControlsTheme); - }, - - _installRequest: function (event) { - let node = event.target; - let data = this._getThemeFromNode(node); - if (!data) - return; - - if (this._isAllowed(node)) { - this._install(data); - return; - } - - let allowButtonText = Strings.browser.GetStringFromName("lwthemeInstallRequest.allowButton"); - let message = Strings.browser.formatStringFromName("lwthemeInstallRequest.message", [node.ownerDocument.location.hostname], 1); - let buttons = [{ - label: allowButtonText, - callback: function () { - LightWeightThemeWebInstaller._install(data); - }, - positive: true - }]; - - NativeWindow.doorhanger.show(message, "Personas", buttons, BrowserApp.selectedTab.id); - }, - - _install: function (newLWTheme) { - this._manager.currentTheme = newLWTheme; - }, - - _previewWindow: null, - _preview: function (event) { - if (!this._isAllowed(event.target)) - return; - let data = this._getThemeFromNode(event.target); - if (!data) - return; - this._resetPreview(); - - this._previewWindow = event.target.ownerDocument.defaultView; - this._previewWindow.addEventListener("pagehide", this, true); - BrowserApp.deck.addEventListener("TabSelect", this, false); - this._manager.previewTheme(data); - }, - - _resetPreview: function (event) { - if (!this._previewWindow || - event && !this._isAllowed(event.target)) - return; - - this._previewWindow.removeEventListener("pagehide", this, true); - this._previewWindow = null; - BrowserApp.deck.removeEventListener("TabSelect", this, false); - - this._manager.resetPreview(); - }, - - _isAllowed: function (node) { - // Make sure the whitelist has been imported to permissions - PermissionsUtils.importFromPrefs("xpinstall.", "install"); - - let pm = Services.perms; - - let uri = node.ownerDocument.documentURIObject; - if (!uri.schemeIs("https")) { - return false; - } - - return pm.testPermission(uri, "install") == pm.ALLOW_ACTION; - }, - - _getThemeFromNode: function (node) { - return this._manager.parseTheme(node.getAttribute("data-browsertheme"), node.baseURI); - } -}; - -var DesktopUserAgent = { - DESKTOP_UA: null, - TCO_DOMAIN: "t.co", - TCO_REPLACE: / Gecko.*/, - - init: function ua_init() { - Services.obs.addObserver(this, "DesktopMode:Change", false); - UserAgentOverrides.addComplexOverride(this.onRequest.bind(this)); - - // See https://developer.mozilla.org/en/Gecko_user_agent_string_reference - this.DESKTOP_UA = Cc["@mozilla.org/network/protocol;1?name=http"] - .getService(Ci.nsIHttpProtocolHandler).userAgent - .replace(/Android \d.+?; [a-zA-Z]+/, "X11; Linux x86_64") - .replace(/Gecko\/[0-9\.]+/, "Gecko/20100101"); - }, - - onRequest: function(channel, defaultUA) { - if (AppConstants.NIGHTLY_BUILD && this.TCO_DOMAIN == channel.URI.host) { - // Force the referrer - channel.referrer = channel.URI; - - // Send a bot-like UA to t.co to get a real redirect. We strip off the - // "Gecko/x.y Firefox/x.y" part - return defaultUA.replace(this.TCO_REPLACE, ""); - } - - let channelWindow = this._getWindowForRequest(channel); - let tab = BrowserApp.getTabForWindow(channelWindow); - if (tab) { - return this.getUserAgentForTab(tab); - } - - return null; - }, - - getUserAgentForWindow: function ua_getUserAgentForWindow(aWindow) { - let tab = BrowserApp.getTabForWindow(aWindow.top); - if (tab) { - return this.getUserAgentForTab(tab); - } - - return null; - }, - - getUserAgentForTab: function ua_getUserAgentForTab(aTab) { - // Send desktop UA if "Request Desktop Site" is enabled. - if (aTab.desktopMode) { - return this.DESKTOP_UA; - } - - return null; - }, - - _getRequestLoadContext: function ua_getRequestLoadContext(aRequest) { - if (aRequest && aRequest.notificationCallbacks) { - try { - return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext); - } catch (ex) { } - } - - if (aRequest && aRequest.loadGroup && aRequest.loadGroup.notificationCallbacks) { - try { - return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); - } catch (ex) { } - } - - return null; - }, - - _getWindowForRequest: function ua_getWindowForRequest(aRequest) { - let loadContext = this._getRequestLoadContext(aRequest); - if (loadContext) { - try { - return loadContext.associatedWindow; - } catch (e) { - // loadContext.associatedWindow can throw when there's no window - } - } - return null; - }, - - observe: function ua_observe(aSubject, aTopic, aData) { - if (aTopic === "DesktopMode:Change") { - let args = JSON.parse(aData); - let tab = BrowserApp.getTabForId(args.tabId); - if (tab) { - tab.reloadWithMode(args.desktopMode); - } - } - } -}; - - -function nsBrowserAccess() { -} - -nsBrowserAccess.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]), - - _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aFlags) { - let isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); - if (isExternal && aURI && aURI.schemeIs("chrome")) - return null; - - let loadflags = isExternal ? - Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : - Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { - if (isExternal) { - aWhere = Services.prefs.getIntPref("browser.link.open_external"); - } else { - aWhere = Services.prefs.getIntPref("browser.link.open_newwindow"); - } - } - - Services.io.offline = false; - - let referrer; - if (aOpener) { - try { - let location = aOpener.location; - referrer = Services.io.newURI(location, null, null); - } catch(e) { } - } - - let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - let pinned = false; - - if (aURI && aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB) { - pinned = true; - let spec = aURI.spec; - let tabs = BrowserApp.tabs; - for (let i = 0; i < tabs.length; i++) { - let appOrigin = ss.getTabValue(tabs[i], "appOrigin"); - if (appOrigin == spec) { - let tab = tabs[i]; - BrowserApp.selectTab(tab); - return tab.browser; - } - } - } - - // If OPEN_SWITCHTAB was not handled above, we need to open a new tab, - // along with other OPEN_ values that create a new tab. - let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW || - aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB || - aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB); - let isPrivate = false; - - if (newTab) { - let parentId = -1; - if (!isExternal && aOpener) { - let parent = BrowserApp.getTabForWindow(aOpener.top); - if (parent) { - parentId = parent.id; - isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser); - } - } - - let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener; - // BrowserApp.addTab calls loadURIWithFlags with the appropriate params - let tab = BrowserApp.addTab(aURI ? aURI.spec : "about:blank", { flags: loadflags, - referrerURI: referrer, - external: isExternal, - parentId: parentId, - opener: openerWindow, - selected: true, - isPrivate: isPrivate, - pinned: pinned }); - - return tab.browser; - } - - // OPEN_CURRENTWINDOW and illegal values - let browser = BrowserApp.selectedBrowser; - if (aURI && browser) { - browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null); - } - - return browser; - }, - - openURI: function browser_openURI(aURI, aOpener, aWhere, aFlags) { - let browser = this._getBrowser(aURI, aOpener, aWhere, aFlags); - return browser ? browser.contentWindow : null; - }, - - openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags) { - let browser = this._getBrowser(aURI, null, aWhere, aFlags); - return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null; - }, - - isTabContentWindow: function(aWindow) { - return BrowserApp.getBrowserForWindow(aWindow) != null; - }, - - canClose() { - return BrowserUtils.canCloseWindow(window); - }, -}; - - -function Tab(aURL, aParams) { - this.filter = null; - this.browser = null; - this.id = 0; - this.lastTouchedAt = Date.now(); - this._zoom = 1.0; - this._drawZoom = 1.0; - this._restoreZoom = false; - this.userScrollPos = { x: 0, y: 0 }; - this.contentDocumentIsDisplayed = true; - this.pluginDoorhangerTimeout = null; - this.shouldShowPluginDoorhanger = true; - this.clickToPlayPluginsActivated = false; - this.desktopMode = false; - this.originalURI = null; - this.hasTouchListener = false; - this.playingAudio = false; - - this.create(aURL, aParams); -} - -/* - * Sanity limit for URIs passed to UI code. - * - * 2000 is the typical industry limit, largely due to older IE versions. - * - * We use 25000, so we'll allow almost any value through. - * - * Still, this truncation doesn't affect history, so this is only a practical - * concern in two ways: the truncated value is used when editing URIs, and as - * the key for favicon fetches. - */ -const MAX_URI_LENGTH = 25000; - -/* - * Similar restriction for titles. This is only a display concern. - */ -const MAX_TITLE_LENGTH = 255; - -/** - * Ensure that a string is of a sane length. - */ -function truncate(text, max) { - if (!text || !max) { - return text; - } - - if (text.length <= max) { - return text; - } - - return text.slice(0, max) + "…"; -} - -Tab.prototype = { - create: function(aURL, aParams) { - if (this.browser) - return; - - aParams = aParams || {}; - - this.browser = document.createElement("browser"); - this.browser.setAttribute("type", "content-targetable"); - this.browser.setAttribute("messagemanagergroup", "browsers"); - - if (Preferences.get("browser.tabs.remote.force-enable", false)) { - this.browser.setAttribute("remote", "true"); - } - - this.browser.permanentKey = {}; - - // Check if we have a "parent" window which we need to set as our opener - if ("opener" in aParams) { - this.browser.presetOpenerWindow(aParams.opener); - } - - // Make sure the previously selected panel remains selected. The selected panel of a deck is - // not stable when panels are added. - let selectedPanel = BrowserApp.deck.selectedPanel; - BrowserApp.deck.insertBefore(this.browser, aParams.sibling || null); - BrowserApp.deck.selectedPanel = selectedPanel; - - let attrs = {}; - if (BrowserApp.manifestUrl) { - let appsService = Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService); - let manifest = appsService.getAppByManifestURL(BrowserApp.manifestUrl); - if (manifest) { - let app = manifest.QueryInterface(Ci.mozIApplication); - this.browser.docShell.frameType = Ci.nsIDocShell.FRAME_TYPE_APP; - attrs['appId'] = app.localId; - } - } - - // Must be called after appendChild so the docShell has been created. - this.setActive(false); - - let isPrivate = ("isPrivate" in aParams) && aParams.isPrivate; - if (isPrivate) { - attrs['privateBrowsingId'] = 1; - } - - this.browser.docShell.setOriginAttributes(attrs); - - // Set the new docShell load flags based on network state. - if (Tabs.useCache) { - this.browser.docShell.defaultLoadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE; - } - - this.browser.stop(); - - // Only set tab uri if uri is valid - let uri = null; - let title = aParams.title || aURL; - try { - uri = Services.io.newURI(aURL, null, null).spec; - } catch (e) {} - - // When the tab is stubbed from Java, there's a window between the stub - // creation and the tab creation in Gecko where the stub could be removed - // or the selected tab can change (which is easiest to hit during startup). - // To prevent these races, we need to differentiate between tab stubs from - // Java and new tabs from Gecko. - let stub = false; - - if (!aParams.zombifying) { - if ("tabID" in aParams) { - this.id = aParams.tabID; - stub = true; - } else { - let jenv = JNI.GetForThread(); - let jTabs = JNI.LoadClass(jenv, "org.mozilla.gecko.Tabs", { - static_methods: [ - { name: "getNextTabId", sig: "()I" } - ], - }); - this.id = jTabs.getNextTabId(); - JNI.UnloadClasses(jenv); - } - - this.desktopMode = ("desktopMode" in aParams) ? aParams.desktopMode : false; - - let message = { - type: "Tab:Added", - tabID: this.id, - uri: truncate(uri, MAX_URI_LENGTH), - parentId: ("parentId" in aParams) ? aParams.parentId : -1, - tabIndex: ("tabIndex" in aParams) ? aParams.tabIndex : -1, - external: ("external" in aParams) ? aParams.external : false, - selected: ("selected" in aParams || aParams.cancelEditMode === true) ? aParams.selected : true, - cancelEditMode: aParams.cancelEditMode === true, - title: truncate(title, MAX_TITLE_LENGTH), - delayLoad: aParams.delayLoad || false, - desktopMode: this.desktopMode, - isPrivate: isPrivate, - stub: stub - }; - Messaging.sendRequest(message); - } - - let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL | - Ci.nsIWebProgress.NOTIFY_LOCATION | - Ci.nsIWebProgress.NOTIFY_SECURITY; - this.filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"].createInstance(Ci.nsIWebProgress); - this.filter.addProgressListener(this, flags) - this.browser.addProgressListener(this.filter, flags); - this.browser.sessionHistory.addSHistoryListener(this); - - this.browser.addEventListener("DOMContentLoaded", this, true); - this.browser.addEventListener("DOMFormHasPassword", this, true); - this.browser.addEventListener("DOMInputPasswordAdded", this, true); - this.browser.addEventListener("DOMLinkAdded", this, true); - this.browser.addEventListener("DOMLinkChanged", this, true); - this.browser.addEventListener("DOMMetaAdded", this, false); - this.browser.addEventListener("DOMTitleChanged", this, true); - this.browser.addEventListener("DOMAudioPlaybackStarted", this, true); - this.browser.addEventListener("DOMAudioPlaybackStopped", this, true); - this.browser.addEventListener("DOMWindowClose", this, true); - this.browser.addEventListener("DOMWillOpenModalDialog", this, true); - this.browser.addEventListener("DOMAutoComplete", this, true); - this.browser.addEventListener("blur", this, true); - this.browser.addEventListener("pageshow", this, true); - this.browser.addEventListener("MozApplicationManifest", this, true); - this.browser.addEventListener("TabPreZombify", this, true); - - // Note that the XBL binding is untrusted - this.browser.addEventListener("PluginBindingAttached", this, true, true); - this.browser.addEventListener("VideoBindingAttached", this, true, true); - this.browser.addEventListener("VideoBindingCast", this, true, true); - - Services.obs.addObserver(this, "before-first-paint", false); - Services.obs.addObserver(this, "media-playback", false); - Services.obs.addObserver(this, "media-playback-resumed", false); - - // Always intialise new tabs with basic session store data to avoid - // problems with functions that always expect it to be present - this.browser.__SS_data = { - entries: [{ - url: aURL, - title: truncate(title, MAX_TITLE_LENGTH) - }], - index: 1, - desktopMode: this.desktopMode, - isPrivate: isPrivate - }; - - if (aParams.delayLoad) { - // If this is a zombie tab, mark the browser for delay loading, which will - // restore the tab when selected using the session data added above - this.browser.__SS_restore = true; - } else { - let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null; - let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null; - let charset = "charset" in aParams ? aParams.charset : null; - - // The search term the user entered to load the current URL - this.userRequested = "userRequested" in aParams ? aParams.userRequested : ""; - this.isSearch = "isSearch" in aParams ? aParams.isSearch : false; - - try { - this.browser.loadURIWithFlags(aURL, flags, referrerURI, charset, postData); - } catch(e) { - let message = { - type: "Content:LoadError", - tabID: this.id - }; - Messaging.sendRequest(message); - dump("Handled load error: " + e); - } - } - }, - - /** - * Reloads the tab with the desktop mode setting. - */ - reloadWithMode: function (aDesktopMode) { - // notify desktopmode for PIDOMWindow - let win = this.browser.contentWindow; - let dwi = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - dwi.setDesktopModeViewport(aDesktopMode); - - // Set desktop mode for tab and send change to Java - if (this.desktopMode != aDesktopMode) { - this.desktopMode = aDesktopMode; - Messaging.sendRequest({ - type: "DesktopMode:Changed", - desktopMode: aDesktopMode, - tabID: this.id - }); - } - - // Only reload the page for http/https schemes - let currentURI = this.browser.currentURI; - if (!currentURI.schemeIs("http") && !currentURI.schemeIs("https")) - return; - - let url = currentURI.spec; - // We need LOAD_FLAGS_BYPASS_CACHE here since we're changing the User-Agent - // string, and servers typically don't use the Vary: User-Agent header, so - // not doing this means that we'd get some of the previously cached content. - let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE | - Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; - if (this.originalURI && !this.originalURI.equals(currentURI)) { - // We were redirected; reload the original URL - url = this.originalURI.spec; - } - - this.browser.docShell.loadURI(url, flags, null, null, null); - }, - - destroy: function() { - if (!this.browser) - return; - - this.browser.removeProgressListener(this.filter); - this.filter.removeProgressListener(this); - this.filter = null; - this.browser.sessionHistory.removeSHistoryListener(this); - - this.browser.removeEventListener("DOMContentLoaded", this, true); - this.browser.removeEventListener("DOMFormHasPassword", this, true); - this.browser.removeEventListener("DOMInputPasswordAdded", this, true); - this.browser.removeEventListener("DOMLinkAdded", this, true); - this.browser.removeEventListener("DOMLinkChanged", this, true); - this.browser.removeEventListener("DOMMetaAdded", this, false); - this.browser.removeEventListener("DOMTitleChanged", this, true); - this.browser.removeEventListener("DOMAudioPlaybackStarted", this, true); - this.browser.removeEventListener("DOMAudioPlaybackStopped", this, true); - this.browser.removeEventListener("DOMWindowClose", this, true); - this.browser.removeEventListener("DOMWillOpenModalDialog", this, true); - this.browser.removeEventListener("DOMAutoComplete", this, true); - this.browser.removeEventListener("blur", this, true); - this.browser.removeEventListener("pageshow", this, true); - this.browser.removeEventListener("MozApplicationManifest", this, true); - this.browser.removeEventListener("TabPreZombify", this, true); - - this.browser.removeEventListener("PluginBindingAttached", this, true, true); - this.browser.removeEventListener("VideoBindingAttached", this, true, true); - this.browser.removeEventListener("VideoBindingCast", this, true, true); - - Services.obs.removeObserver(this, "before-first-paint"); - Services.obs.removeObserver(this, "media-playback", false); - Services.obs.removeObserver(this, "media-playback-resumed", false); - - // Make sure the previously selected panel remains selected. The selected panel of a deck is - // not stable when panels are removed. - let selectedPanel = BrowserApp.deck.selectedPanel; - BrowserApp.deck.removeChild(this.browser); - BrowserApp.deck.selectedPanel = selectedPanel; - - this.browser = null; - }, - - // This should be called to update the browser when the tab gets selected/unselected - setActive: function setActive(aActive) { - if (!this.browser || !this.browser.docShell) - return; - - this.lastTouchedAt = Date.now(); - - if (aActive) { - this.browser.setAttribute("type", "content-primary"); - this.browser.focus(); - this.browser.docShellIsActive = true; - Reader.updatePageAction(this); - ExternalApps.updatePageAction(this.browser.currentURI, this.browser.contentDocument); - } else { - this.browser.setAttribute("type", "content-targetable"); - this.browser.docShellIsActive = false; - this.browser.blur(); - } - }, - - getActive: function getActive() { - return this.browser.docShellIsActive; - }, - - // These constants are used to prioritize high quality metadata over low quality data, so that - // we can collect data as we find meta tags, and replace low quality metadata with higher quality - // matches. For instance a msApplicationTile icon is a better tile image than an og:image tag. - METADATA_GOOD_MATCH: 10, - METADATA_NORMAL_MATCH: 1, - - addMetadata: function(type, value, quality = 1) { - if (!this.metatags) { - this.metatags = { - url: this.browser.currentURI.specIgnoringRef - }; - } - - if (type == "touchIconList") { - if (!this.metatags['touchIconList']) { - this.metatags['touchIconList'] = {}; - } - this.metatags.touchIconList[quality] = value; - } else if (!this.metatags[type] || this.metatags[type + "_quality"] < quality) { - this.metatags[type] = value; - this.metatags[type + "_quality"] = quality; - } - }, - - sanitizeRelString: function(linkRel) { - // Sanitize the rel string - let list = []; - if (linkRel) { - list = linkRel.toLowerCase().split(/\s+/); - let hash = {}; - list.forEach(function(value) { hash[value] = true; }); - list = []; - for (let rel in hash) - list.push("[" + rel + "]"); - } - return list; - }, - - makeFaviconMessage: function(eventTarget) { - // We want to get the largest icon size possible for our UI. - let maxSize = 0; - - // We use the sizes attribute if available - // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon - if (eventTarget.hasAttribute("sizes")) { - let sizes = eventTarget.getAttribute("sizes").toLowerCase(); - - if (sizes == "any") { - // Since Java expects an integer, use -1 to represent icons with sizes="any" - maxSize = -1; - } else { - let tokens = sizes.split(" "); - tokens.forEach(function(token) { - // TODO: check for invalid tokens - let [w, h] = token.split("x"); - maxSize = Math.max(maxSize, Math.max(w, h)); - }); - } - } - return { - type: "Link:Favicon", - tabID: this.id, - href: resolveGeckoURI(eventTarget.href), - size: maxSize, - mime: eventTarget.getAttribute("type") || "" - }; - }, - - makeFeedMessage: function(eventTarget, targetType) { - try { - // urlSecurityCeck will throw if things are not OK - ContentAreaUtils.urlSecurityCheck(eventTarget.href, - eventTarget.ownerDocument.nodePrincipal, - Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); - - if (!this.browser.feeds) - this.browser.feeds = []; - - this.browser.feeds.push({ - href: eventTarget.href, - title: eventTarget.title, - type: targetType - }); - - return { - type: "Link:Feed", - tabID: this.id - }; - } catch (e) { - return null; - } - }, - - sendOpenSearchMessage: function(eventTarget) { - let type = eventTarget.type && eventTarget.type.toLowerCase(); - // Replace all starting or trailing spaces or spaces before "*;" globally w/ "". - type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); - - // Check that type matches opensearch. - let isOpenSearch = (type == "application/opensearchdescription+xml"); - if (isOpenSearch && eventTarget.title && /^(?:https?|ftp):/i.test(eventTarget.href)) { - Services.search.init(() => { - let visibleEngines = Services.search.getVisibleEngines(); - // NOTE: Engines are currently identified by name, but this can be changed - // when Engines are identified by URL (see bug 335102). - if (visibleEngines.some(function(e) { - return e.name == eventTarget.title; - })) { - // This engine is already present, do nothing. - return null; - } - - if (this.browser.engines) { - // This engine has already been handled, do nothing. - if (this.browser.engines.some(function(e) { - return e.url == eventTarget.href; - })) { - return null; - } - } else { - this.browser.engines = []; - } - - // Get favicon. - let iconURL = eventTarget.ownerDocument.documentURIObject.prePath + "/favicon.ico"; - - let newEngine = { - title: eventTarget.title, - url: eventTarget.href, - iconURL: iconURL - }; - - this.browser.engines.push(newEngine); - - // Don't send a message to display engines if we've already handled an engine. - if (this.browser.engines.length > 1) - return null; - - // Broadcast message that this tab contains search engines that should be visible. - Messaging.sendRequest({ - type: "Link:OpenSearch", - tabID: this.id, - visible: true - }); - }); - } - }, - - handleEvent: function(aEvent) { - switch (aEvent.type) { - case "DOMContentLoaded": { - let target = aEvent.originalTarget; - - // ignore on frames and other documents - if (target != this.browser.contentDocument) - return; - - // Sample the background color of the page and pass it along. (This is used to draw the - // checkerboard.) Right now we don't detect changes in the background color after this - // event fires; it's not clear that doing so is worth the effort. - var backgroundColor = null; - try { - let { contentDocument, contentWindow } = this.browser; - let computedStyle = contentWindow.getComputedStyle(contentDocument.body); - backgroundColor = computedStyle.backgroundColor; - } catch (e) { - // Ignore. Catching and ignoring exceptions here ensures that Talos succeeds. - } - - let docURI = target.documentURI; - let errorType = ""; - if (docURI.startsWith("about:certerror")) { - errorType = "certerror"; - } - else if (docURI.startsWith("about:blocked")) { - errorType = "blocked"; - } - else if (docURI.startsWith("about:neterror")) { - let error = docURI.search(/e\=/); - let duffUrl = docURI.search(/\&u\=/); - let errorExtra = decodeURIComponent(docURI.slice(error + 2, duffUrl)); - // Here is a list of errorExtra types (et_*) - // http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/netError.xhtml#287 - UITelemetry.addEvent("neterror.1", "content", null, errorExtra); - errorType = "neterror"; - } - - // Attach a listener to watch for "click" events bubbling up from error - // pages and other similar page. This lets us fix bugs like 401575 which - // require error page UI to do privileged things, without letting error - // pages have any privilege themselves. - if (docURI.startsWith("about:neterror")) { - NetErrorHelper.attachToBrowser(this.browser); - } - - Messaging.sendRequest({ - type: "DOMContentLoaded", - tabID: this.id, - bgColor: backgroundColor, - errorType: errorType, - metadata: this.metatags, - }); - - // Reset isSearch so that the userRequested term will be erased on next page load - this.metatags = null; - - if (docURI.startsWith("about:certerror") || docURI.startsWith("about:blocked")) { - this.browser.addEventListener("click", ErrorPageEventHandler, true); - let listener = function() { - this.browser.removeEventListener("click", ErrorPageEventHandler, true); - this.browser.removeEventListener("pagehide", listener, true); - }.bind(this); - - this.browser.addEventListener("pagehide", listener, true); - } - - if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_ANDROID_ACTIVITY_STREAM) { - WebsiteMetadata.parseAsynchronously(this.browser.contentDocument); - } - - break; - } - - case "DOMFormHasPassword": { - LoginManagerContent.onDOMFormHasPassword(aEvent, - this.browser.contentWindow); - - // Send logins for this hostname to Java. - let hostname = aEvent.target.baseURIObject.prePath; - let foundLogins = Services.logins.findLogins({}, hostname, "", ""); - if (foundLogins.length > 0) { - let displayHost = IdentityHandler.getEffectiveHost(); - let title = { text: displayHost, resource: hostname }; - let selectObj = { title: title, logins: foundLogins }; - Messaging.sendRequest({ type: "Doorhanger:Logins", data: selectObj }); - } - break; - } - - case "DOMInputPasswordAdded": { - LoginManagerContent.onDOMInputPasswordAdded(aEvent, - this.browser.contentWindow); - } - - case "DOMMetaAdded": - let target = aEvent.originalTarget; - let browser = BrowserApp.getBrowserForDocument(target.ownerDocument); - - switch (target.name) { - case "msapplication-TileImage": - this.addMetadata("tileImage", browser.currentURI.resolve(target.content), this.METADATA_GOOD_MATCH); - break; - case "msapplication-TileColor": - this.addMetadata("tileColor", target.content, this.METADATA_GOOD_MATCH); - break; - } - - break; - - case "DOMLinkAdded": - case "DOMLinkChanged": { - let jsonMessage = null; - let target = aEvent.originalTarget; - if (!target.href || target.disabled) - return; - - // Ignore on frames and other documents - if (target.ownerDocument != this.browser.contentDocument) - return; - - // Sanitize rel link - let list = this.sanitizeRelString(target.rel); - if (list.indexOf("[icon]") != -1) { - jsonMessage = this.makeFaviconMessage(target); - } else if (list.indexOf("[apple-touch-icon]") != -1 || - list.indexOf("[apple-touch-icon-precomposed]") != -1) { - jsonMessage = this.makeFaviconMessage(target); - jsonMessage['type'] = 'Link:Touchicon'; - this.addMetadata("touchIconList", jsonMessage.href, jsonMessage.size); - } else if (list.indexOf("[alternate]") != -1 && aEvent.type == "DOMLinkAdded") { - let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, ""); - let isFeed = (type == "application/rss+xml" || type == "application/atom+xml"); - - if (!isFeed) - return; - - jsonMessage = this.makeFeedMessage(target, type); - } else if (list.indexOf("[search]") != -1 && aEvent.type == "DOMLinkAdded") { - this.sendOpenSearchMessage(target); - } - if (!jsonMessage) - return; - - Messaging.sendRequest(jsonMessage); - break; - } - - case "DOMTitleChanged": { - if (!aEvent.isTrusted) - return; - - // ignore on frames and other documents - if (aEvent.originalTarget != this.browser.contentDocument) - return; - - Messaging.sendRequest({ - type: "DOMTitleChanged", - tabID: this.id, - title: truncate(aEvent.target.title, MAX_TITLE_LENGTH) - }); - break; - } - - case "TabPreZombify": { - if (!this.playingAudio) { - return; - } - // Fall through to the DOMAudioPlayback events, so the - // audio playback indicator gets reset upon zombification. - } - case "DOMAudioPlaybackStarted": - case "DOMAudioPlaybackStopped": { - if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") || - !aEvent.isTrusted) { - return; - } - - let browser = aEvent.originalTarget; - if (browser != this.browser) { - return; - } - - this.playingAudio = aEvent.type === "DOMAudioPlaybackStarted"; - - Messaging.sendRequest({ - type: "Tab:AudioPlayingChange", - tabID: this.id, - isAudioPlaying: this.playingAudio - }); - return; - } - - case "DOMWindowClose": { - if (!aEvent.isTrusted) - return; - - // Find the relevant tab, and close it from Java - if (this.browser.contentWindow == aEvent.target) { - aEvent.preventDefault(); - - Messaging.sendRequest({ - type: "Tab:Close", - tabID: this.id - }); - } - break; - } - - case "DOMWillOpenModalDialog": { - if (!aEvent.isTrusted) - return; - - // We're about to open a modal dialog, make sure the opening - // tab is brought to the front. - let tab = BrowserApp.getTabForWindow(aEvent.target.top); - BrowserApp.selectTab(tab); - break; - } - - case "DOMAutoComplete": - case "blur": { - LoginManagerContent.onUsernameInput(aEvent); - break; - } - - case "PluginBindingAttached": { - PluginHelper.handlePluginBindingAttached(this, aEvent); - break; - } - - case "VideoBindingAttached": { - CastingApps.handleVideoBindingAttached(this, aEvent); - break; - } - - case "VideoBindingCast": { - CastingApps.handleVideoBindingCast(this, aEvent); - break; - } - - case "MozApplicationManifest": { - OfflineApps.offlineAppRequested(aEvent.originalTarget.defaultView); - break; - } - - case "pageshow": { - LoginManagerContent.onPageShow(aEvent, this.browser.contentWindow); - - // The rest of this only handles pageshow for the top-level document. - if (aEvent.originalTarget.defaultView != this.browser.contentWindow) - return; - - let target = aEvent.originalTarget; - let docURI = target.documentURI; - if (!docURI.startsWith("about:neterror") && !this.isSearch) { - // If this wasn't an error page and the user isn't search, don't retain the typed entry - this.userRequested = ""; - } - - Messaging.sendRequest({ - type: "Content:PageShow", - tabID: this.id, - userRequested: this.userRequested, - fromCache: Tabs.useCache - }); - - this.isSearch = false; - - if (!aEvent.persisted && Services.prefs.getBoolPref("browser.ui.linkify.phone")) { - if (!this._linkifier) - this._linkifier = new Linkifier(); - this._linkifier.linkifyNumbers(this.browser.contentWindow.document); - } - - // Update page actions for helper apps. - let uri = this.browser.currentURI; - if (BrowserApp.selectedTab == this) { - if (ExternalApps.shouldCheckUri(uri)) { - ExternalApps.updatePageAction(uri, this.browser.contentDocument); - } else { - ExternalApps.clearPageAction(); - } - } - } - } - }, - - onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { - let contentWin = aWebProgress.DOMWindow; - if (contentWin != contentWin.top) - return; - - // Filter optimization: Only really send NETWORK state changes to Java listener - if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { - if (AppConstants.NIGHTLY_BUILD && (aStateFlags & Ci.nsIWebProgressListener.STATE_START)) { - Profiler.AddMarker("Load start: " + aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec); - } else if (AppConstants.NIGHTLY_BUILD && (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) && !aWebProgress.isLoadingDocument) { - Profiler.AddMarker("Load stop: " + aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec); - } - - if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) && aWebProgress.isLoadingDocument) { - // We may receive a document stop event while a document is still loading - // (such as when doing URI fixup). Don't notify Java UI in these cases. - return; - } - - // Clear page-specific opensearch engines and feeds for a new request. - if (aStateFlags & Ci.nsIWebProgressListener.STATE_START && aRequest && aWebProgress.isTopLevel) { - this.browser.engines = null; - this.browser.feeds = null; - } - - // true if the page loaded successfully (i.e., no 404s or other errors) - let success = false; - let uri = ""; - try { - // Remember original URI for UA changes on redirected pages - this.originalURI = aRequest.QueryInterface(Components.interfaces.nsIChannel).originalURI; - - if (this.originalURI != null) - uri = this.originalURI.spec; - } catch (e) { } - try { - success = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel).requestSucceeded; - } catch (e) { - // If the request does not handle the nsIHttpChannel interface, use nsIRequest's success - // status. Used for local files. See bug 948849. - success = aRequest.status == 0; - } - - // Check to see if we restoring the content from a previous presentation (session) - // since there should be no real network activity - let restoring = (aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) > 0; - - let message = { - type: "Content:StateChange", - tabID: this.id, - uri: truncate(uri, MAX_URI_LENGTH), - state: aStateFlags, - restoring: restoring, - success: success - }; - Messaging.sendRequest(message); - } - }, - - onLocationChange: function(aWebProgress, aRequest, aLocationURI, aFlags) { - let contentWin = aWebProgress.DOMWindow; - - // Browser webapps may load content inside iframes that can not reach across the app/frame boundary - // i.e. even though the page is loaded in an iframe window.top != webapp - // Make cure this window is a top level tab before moving on. - if (BrowserApp.getBrowserForWindow(contentWin) == null) - return; - - this._hostChanged = true; - - let fixedURI = aLocationURI; - try { - fixedURI = URIFixup.createExposableURI(aLocationURI); - } catch (ex) { } - - // In restricted profiles, we refuse to let you open various urls. - if (!ParentalControls.isAllowed(ParentalControls.BROWSE, fixedURI)) { - aRequest.cancel(Cr.NS_BINDING_ABORTED); - - this.browser.docShell.displayLoadError(Cr.NS_ERROR_UNKNOWN_PROTOCOL, fixedURI, null); - } - - let contentType = contentWin.document.contentType; - - // If fixedURI matches browser.lastURI, we assume this isn't a real location - // change but rather a spurious addition like a wyciwyg URI prefix. See Bug 747883. - // Note that we have to ensure fixedURI is not the same as aLocationURI so we - // don't false-positive page reloads as spurious additions. - let sameDocument = (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) != 0 || - ((this.browser.lastURI != null) && fixedURI.equals(this.browser.lastURI) && !fixedURI.equals(aLocationURI)); - this.browser.lastURI = fixedURI; - - // Let the reader logic know about same document changes because we won't get a DOMContentLoaded - // or pageshow event, but we'll still want to update the reader view button to account for this change. - // This mirrors the desktop logic in TabsProgressListener. - if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { - this.browser.messageManager.sendAsyncMessage("Reader:PushState", {isArticle: this.browser.isArticle}); - } - - // Reset state of click-to-play plugin notifications. - clearTimeout(this.pluginDoorhangerTimeout); - this.pluginDoorhangerTimeout = null; - this.shouldShowPluginDoorhanger = true; - this.clickToPlayPluginsActivated = false; - - let documentURI = contentWin.document.documentURIObject.spec; - - // If reader mode, get the base domain for the original url. - let strippedURI = this._stripAboutReaderURL(documentURI); - - // Borrowed from desktop Firefox: http://hg.mozilla.org/mozilla-central/annotate/72835344333f/browser/base/content/urlbarBindings.xml#l236 - let matchedURL = strippedURI.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/); - let baseDomain = ""; - if (matchedURL) { - var domain = ""; - [, , domain] = matchedURL; - - try { - baseDomain = Services.eTLD.getBaseDomainFromHost(domain); - if (!domain.endsWith(baseDomain)) { - // getBaseDomainFromHost converts its resultant to ACE. - let IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService); - baseDomain = IDNService.convertACEtoUTF8(baseDomain); - } - } catch (e) {} - } - - // If we are navigating to a new location with a different host, - // clear any URL origin that might have been pinned to this tab. - let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); - let appOrigin = ss.getTabValue(this, "appOrigin"); - if (appOrigin) { - let originHost = ""; - try { - originHost = Services.io.newURI(appOrigin, null, null).host; - } catch (e if (e.result == Cr.NS_ERROR_FAILURE)) { - // NS_ERROR_FAILURE can be thrown by nsIURI.host if the URI scheme does not possess a host - in this case - // we just act as if we have an empty host. - } - if (originHost != aLocationURI.host) { - // Note: going 'back' will not make this tab pinned again - ss.deleteTabValue(this, "appOrigin"); - } - } - - // Update the page actions URI for helper apps. - if (BrowserApp.selectedTab == this) { - ExternalApps.updatePageActionUri(fixedURI); - } - - let webNav = contentWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation); - - let message = { - type: "Content:LocationChange", - tabID: this.id, - uri: truncate(fixedURI.spec, MAX_URI_LENGTH), - userRequested: this.userRequested || "", - baseDomain: baseDomain, - contentType: (contentType ? contentType : ""), - sameDocument: sameDocument, - - historyIndex: webNav.sessionHistory.index, - historySize: webNav.sessionHistory.count, - canGoBack: webNav.canGoBack, - canGoForward: webNav.canGoForward, - }; - - Messaging.sendRequest(message); - - if (!sameDocument) { - // XXX This code assumes that this is the earliest hook we have at which - // browser.contentDocument is changed to the new document we're loading - this.contentDocumentIsDisplayed = false; - this.hasTouchListener = false; - Services.obs.notifyObservers(this.browser, "Session:NotifyLocationChange", null); - } - }, - - _stripAboutReaderURL: function (url) { - return ReaderMode.getOriginalUrl(url) || url; - }, - - // Properties used to cache security state used to update the UI - _state: null, - _hostChanged: false, // onLocationChange will flip this bit - - onSecurityChange: function(aWebProgress, aRequest, aState) { - // Don't need to do anything if the data we use to update the UI hasn't changed - if (this._state == aState && !this._hostChanged) - return; - - this._state = aState; - this._hostChanged = false; - - let identity = IdentityHandler.checkIdentity(aState, this.browser); - - let message = { - type: "Content:SecurityChange", - tabID: this.id, - identity: identity - }; - - Messaging.sendRequest(message); - }, - - onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { - // Note: aWebProgess and aRequest will be NULL since we are filtering webprogress - // notifications using nsBrowserStatusFilter. - }, - - onStatusChange: function(aBrowser, aWebProgress, aRequest, aStatus, aMessage) { - // Note: aWebProgess and aRequest will be NULL since we are filtering webprogress - // notifications using nsBrowserStatusFilter. - }, - - _getGeckoZoom: function() { - let res = {}; - let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - cwu.getResolution(res); - let zoom = res.value * window.devicePixelRatio; - return zoom; - }, - - saveSessionZoom: function(aZoom) { - let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - cwu.setResolutionAndScaleTo(aZoom / window.devicePixelRatio); - }, - - restoredSessionZoom: function() { - let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - - if (this._restoreZoom && cwu.isResolutionSet) { - return this._getGeckoZoom(); - } - return null; - }, - - _updateZoomFromHistoryEvent: function(aHistoryEventName) { - // Restore zoom only when moving in session history, not for new page loads. - this._restoreZoom = aHistoryEventName !== "New"; - }, - - OnHistoryNewEntry: function(aUri) { - this._updateZoomFromHistoryEvent("New"); - }, - - OnHistoryGoBack: function(aUri) { - this._updateZoomFromHistoryEvent("Back"); - return true; - }, - - OnHistoryGoForward: function(aUri) { - this._updateZoomFromHistoryEvent("Forward"); - return true; - }, - - OnHistoryReload: function(aUri, aFlags) { - // we don't do anything with this, so don't propagate it - // for now anyway - return true; - }, - - OnHistoryGotoIndex: function(aIndex, aUri) { - this._updateZoomFromHistoryEvent("Goto"); - return true; - }, - - OnHistoryPurge: function(aNumEntries) { - this._updateZoomFromHistoryEvent("Purge"); - return true; - }, - - OnHistoryReplaceEntry: function(aIndex) { - // we don't do anything with this, so don't propogate it - // for now anyway. - }, - - ShouldNotifyMediaPlaybackChange: function(activeState) { - // If the media is active, we would check it's duration, because we don't - // want to show the media control interface for the short sound which - // duration is smaller than the threshold. The basic unit is second. - // Note : the streaming format's duration is infinite. - if (activeState === "inactive") { - return true; - } - - const mediaDurationThreshold = 1.0; - - let audioElements = this.browser.contentDocument.getElementsByTagName("audio"); - for each (let audio in audioElements) { - if (!audio.paused && audio.duration < mediaDurationThreshold) { - return false; - } - } - - let videoElements = this.browser.contentDocument.getElementsByTagName("video"); - for each (let video in videoElements) { - if (!video.paused && video.duration < mediaDurationThreshold) { - return false; - } - } - - return true; - }, - - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "before-first-paint": - // Is it on the top level? - let contentDocument = aSubject; - if (contentDocument == this.browser.contentDocument) { - if (BrowserApp.selectedTab == this) { - BrowserApp.contentDocumentChanged(); - } - this.contentDocumentIsDisplayed = true; - - if (contentDocument instanceof Ci.nsIImageDocument) { - contentDocument.shrinkToFit(); - } - } - break; - - case "media-playback": - case "media-playback-resumed": - if (!aSubject) { - return; - } - - let winId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (this.browser.outerWindowID != winId) { - return; - } - - if (!this.ShouldNotifyMediaPlaybackChange(aData)) { - return; - } - - let status; - if (aTopic == "media-playback") { - status = (aData === "inactive") ? "end" : "start"; - } else if (aTopic == "media-playback-resumed") { - status = "resume"; - } - - Messaging.sendRequest({ - type: "Tab:MediaPlaybackChange", - tabID: this.id, - status: status - }); - break; - } - }, - - // nsIBrowserTab - get window() { - if (!this.browser) - return null; - return this.browser.contentWindow; - }, - - get scale() { - return this._zoom; - }, - - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsIWebProgressListener, - Ci.nsISHistoryListener, - Ci.nsIObserver, - Ci.nsISupportsWeakReference, - Ci.nsIBrowserTab - ]) -}; - -var BrowserEventHandler = { - init: function init() { - this._clickInZoomedView = false; - Services.obs.addObserver(this, "Gesture:SingleTap", false); - Services.obs.addObserver(this, "Gesture:ClickInZoomedView", false); - - BrowserApp.deck.addEventListener("touchend", this, true); - - BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false); - BrowserApp.deck.addEventListener("MozMouseHittest", this, true); - BrowserApp.deck.addEventListener("OpenMediaWithExternalApp", this, true); - - InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true)); - InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true)); - - // ReaderViews support backPress listeners. - Messaging.addListener(() => { - return Reader.onBackPress(BrowserApp.selectedTab.id); - }, "Browser:OnBackPressed"); - }, - - handleEvent: function(aEvent) { - switch (aEvent.type) { - case 'touchend': - if (this._inCluster) { - aEvent.preventDefault(); - } - break; - case 'MozMouseHittest': - this._handleRetargetedTouchStart(aEvent); - break; - case 'OpenMediaWithExternalApp': { - let mediaSrc = aEvent.target.currentSrc || aEvent.target.src; - let uuid = uuidgen.generateUUID().toString(); - Services.androidBridge.handleGeckoMessage({ - type: "Video:Play", - uri: mediaSrc, - uuid: uuid - }); - break; - } - } - }, - - _handleRetargetedTouchStart: function(aEvent) { - // we should only get this called just after a new touchstart with a single - // touch point. - if (!BrowserApp.isBrowserContentDocumentDisplayed() || aEvent.defaultPrevented) { - return; - } - - let target = aEvent.target; - if (!target) { - return; - } - - this._inCluster = aEvent.hitCluster; - if (this._inCluster) { - return; // No highlight for a cluster of links - } - - let uri = this._getLinkURI(target); - if (uri) { - try { - Services.io.QueryInterface(Ci.nsISpeculativeConnect).speculativeConnect(uri, null); - } catch (e) {} - } - this._doTapHighlight(target); - }, - - _getLinkURI: function(aElement) { - if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && - ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) || - (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href))) { - try { - return Services.io.newURI(aElement.href, null, null); - } catch (e) {} - } - return null; - }, - - observe: function(aSubject, aTopic, aData) { - // the remaining events are all dependent on the browser content document being the - // same as the browser displayed document. if they are not the same, we should ignore - // the event. - if (BrowserApp.isBrowserContentDocumentDisplayed()) { - this.handleUserEvent(aTopic, aData); - } - }, - - handleUserEvent: function(aTopic, aData) { - switch (aTopic) { - - case "Gesture:ClickInZoomedView": - this._clickInZoomedView = true; - break; - - case "Gesture:SingleTap": { - let focusedElement = BrowserApp.getFocusedInput(BrowserApp.selectedBrowser); - let data = JSON.parse(aData); - let {x, y} = data; - - if (this._inCluster && this._clickInZoomedView != true) { - // If there is a focused element, the display of the zoomed view won't remove the focus. - // In this case, the form assistant linked to the focused element will never be closed. - // To avoid this situation, the focus is moved and the form assistant is closed. - if (focusedElement) { - try { - Services.focus.moveFocus(BrowserApp.selectedBrowser.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0); - } catch(e) { - Cu.reportError(e); - } - Messaging.sendRequest({ type: "FormAssist:Hide" }); - } - this._clusterClicked(x, y); - } else { - if (this._clickInZoomedView != true) { - this._closeZoomedView(); - } - } - this._clickInZoomedView = false; - this._cancelTapHighlight(); - break; - } - - default: - dump('BrowserEventHandler.handleUserEvent: unexpected topic "' + aTopic + '"'); - break; - } - }, - - _closeZoomedView: function() { - Messaging.sendRequest({ - type: "Gesture:CloseZoomedView" - }); - }, - - _clusterClicked: function(aX, aY) { - Messaging.sendRequest({ - type: "Gesture:clusteredLinksClicked", - clickPosition: { - x: aX, - y: aY - } - }); - }, - - _highlightElement: null, - - _doTapHighlight: function _doTapHighlight(aElement) { - this._highlightElement = aElement; - }, - - _cancelTapHighlight: function _cancelTapHighlight() { - if (!this._highlightElement) - return; - - this._highlightElement = null; - } -}; - -const ElementTouchHelper = { - getBoundingContentRect: function(aElement) { - if (!aElement) - return {x: 0, y: 0, w: 0, h: 0}; - - let document = aElement.ownerDocument; - while (document.defaultView.frameElement) - document = document.defaultView.frameElement.ownerDocument; - - let cwu = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - let scrollX = {}, scrollY = {}; - cwu.getScrollXY(false, scrollX, scrollY); - - let r = aElement.getBoundingClientRect(); - - // step out of iframes and frames, offsetting scroll values - for (let frame = aElement.ownerDocument.defaultView; frame.frameElement && frame != content; frame = frame.parent) { - // adjust client coordinates' origin to be top left of iframe viewport - let rect = frame.frameElement.getBoundingClientRect(); - let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth; - let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth; - scrollX.value += rect.left + parseInt(left); - scrollY.value += rect.top + parseInt(top); - } - - return {x: r.left + scrollX.value, - y: r.top + scrollY.value, - w: r.width, - h: r.height }; - } -}; - -var ErrorPageEventHandler = { - handleEvent: function(aEvent) { - switch (aEvent.type) { - case "click": { - // Don't trust synthetic events - if (!aEvent.isTrusted) - return; - - let target = aEvent.originalTarget; - let errorDoc = target.ownerDocument; - - // If the event came from an ssl error page, it is probably either the "Add - // Exception…" or "Get me out of here!" button - if (errorDoc.documentURI.startsWith("about:certerror?e=nssBadCert")) { - let perm = errorDoc.getElementById("permanentExceptionButton"); - let temp = errorDoc.getElementById("temporaryExceptionButton"); - if (target == temp || target == perm) { - // Handle setting an cert exception and reloading the page - try { - // Add a new SSL exception for this URL - let uri = Services.io.newURI(errorDoc.location.href, null, null); - let sslExceptions = new SSLExceptions(); - - if (target == perm) - sslExceptions.addPermanentException(uri, errorDoc.defaultView); - else - sslExceptions.addTemporaryException(uri, errorDoc.defaultView); - } catch (e) { - dump("Failed to set cert exception: " + e + "\n"); - } - errorDoc.location.reload(); - } else if (target == errorDoc.getElementById("getMeOutOfHereButton")) { - errorDoc.location = "about:home"; - } - } else if (errorDoc.documentURI.startsWith("about:blocked")) { - // The event came from a button on a malware/phishing block page - // First check whether it's malware, phishing or unwanted, so that we - // can use the right strings/links - let isIframe = (errorDoc.defaultView.parent === errorDoc.defaultView); - let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); - - if (target == errorDoc.getElementById("getMeOutButton")) { - errorDoc.location = "about:home"; - } else if (target == errorDoc.getElementById("reportButton")) { - // This is the "Why is this site blocked" button. We redirect - // to the generic page describing phishing/malware protection. - let url = Services.urlFormatter.formatURLPref("app.support.baseURL"); - BrowserApp.selectedBrowser.loadURI(url + "phishing-malware"); - } else if (target == errorDoc.getElementById("ignoreWarningButton") && - Services.prefs.getBoolPref("browser.safebrowsing.allowOverride")) { - // Allow users to override and continue through to the site, - let webNav = BrowserApp.selectedBrowser.docShell.QueryInterface(Ci.nsIWebNavigation); - let location = BrowserApp.selectedBrowser.contentWindow.location; - webNav.loadURI(location, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, null, null, null); - - // ....but add a notify bar as a reminder, so that they don't lose - // track after, e.g., tab switching. - NativeWindow.doorhanger.show(Strings.browser.GetStringFromName("safeBrowsingDoorhanger"), "safebrowsing-warning", [], BrowserApp.selectedTab.id); - } - } - break; - } - } - } -}; - -var FormAssistant = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]), - - // Used to keep track of the element that corresponds to the current - // autocomplete suggestions - _currentInputElement: null, - - // The value of the currently focused input - _currentInputValue: null, - - // Whether we're in the middle of an autocomplete - _doingAutocomplete: false, - - // Keep track of whether or not an invalid form has been submitted - _invalidSubmit: false, - - init: function() { - Services.obs.addObserver(this, "FormAssist:AutoComplete", false); - Services.obs.addObserver(this, "FormAssist:Hidden", false); - Services.obs.addObserver(this, "FormAssist:Remove", false); - Services.obs.addObserver(this, "invalidformsubmit", false); - Services.obs.addObserver(this, "PanZoom:StateChange", false); - - // We need to use a capturing listener for focus events - BrowserApp.deck.addEventListener("focus", this, true); - BrowserApp.deck.addEventListener("blur", this, true); - BrowserApp.deck.addEventListener("click", this, true); - BrowserApp.deck.addEventListener("input", this, false); - BrowserApp.deck.addEventListener("pageshow", this, false); - }, - - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "PanZoom:StateChange": - // If the user is just touching the screen and we haven't entered a pan or zoom state yet do nothing - if (aData == "TOUCHING" || aData == "WAITING_LISTENERS") - break; - if (aData == "NOTHING") { - // only look for input elements, not contentEditable or multiline text areas - let focused = BrowserApp.getFocusedInput(BrowserApp.selectedBrowser, true); - if (!focused) - break; - - if (this._showValidationMessage(focused)) - break; - let checkResultsClick = hasResults => { - if (!hasResults) { - this._hideFormAssistPopup(); - } - }; - this._showAutoCompleteSuggestions(focused, checkResultsClick); - } else { - // temporarily hide the form assist popup while we're panning or zooming the page - this._hideFormAssistPopup(); - } - break; - case "FormAssist:AutoComplete": - if (!this._currentInputElement) - break; - - let editableElement = this._currentInputElement.QueryInterface(Ci.nsIDOMNSEditableElement); - - this._doingAutocomplete = true; - - // If we have an active composition string, commit it before sending - // the autocomplete event with the text that will replace it. - try { - let imeEditor = editableElement.editor.QueryInterface(Ci.nsIEditorIMESupport); - if (imeEditor.composing) - imeEditor.forceCompositionEnd(); - } catch (e) {} - - editableElement.setUserInput(aData); - this._currentInputValue = aData; - - let event = this._currentInputElement.ownerDocument.createEvent("Events"); - event.initEvent("DOMAutoComplete", true, true); - this._currentInputElement.dispatchEvent(event); - - this._doingAutocomplete = false; - - break; - - case "FormAssist:Hidden": - this._currentInputElement = null; - break; - - case "FormAssist:Remove": - if (!this._currentInputElement) { - break; - } - - FormHistory.update({ - op: "remove", - fieldname: this._currentInputElement.name, - value: aData - }); - break; - } - }, - - notifyInvalidSubmit: function notifyInvalidSubmit(aFormElement, aInvalidElements) { - if (!aInvalidElements.length) - return; - - // Ignore this notificaiton if the current tab doesn't contain the invalid element - let currentElement = aInvalidElements.queryElementAt(0, Ci.nsISupports); - if (BrowserApp.selectedBrowser.contentDocument != - currentElement.ownerDocument.defaultView.top.document) - return; - - this._invalidSubmit = true; - - // Our focus listener will show the element's validation message - currentElement.focus(); - }, - - handleEvent: function(aEvent) { - switch (aEvent.type) { - case "focus": { - let currentElement = aEvent.target; - - // Only show a validation message on focus. - this._showValidationMessage(currentElement); - break; - } - - case "blur": { - this._currentInputValue = null; - break; - } - - case "click": { - let currentElement = aEvent.target; - - // Prioritize a form validation message over autocomplete suggestions - // when the element is first focused (a form validation message will - // only be available if an invalid form was submitted) - if (this._showValidationMessage(currentElement)) - break; - - let checkResultsClick = hasResults => { - if (!hasResults) { - this._hideFormAssistPopup(); - } - }; - - this._showAutoCompleteSuggestions(currentElement, checkResultsClick); - break; - } - - case "input": { - let currentElement = aEvent.target; - - // If this element isn't focused, we're already in middle of an - // autocomplete, or its value hasn't changed, don't show the - // autocomplete popup. - if (currentElement !== BrowserApp.getFocusedInput(BrowserApp.selectedBrowser) || - this._doingAutocomplete || - currentElement.value === this._currentInputValue) { - break; - } - - this._currentInputValue = currentElement.value; - - // Since we can only show one popup at a time, prioritze autocomplete - // suggestions over a form validation message - let checkResultsInput = hasResults => { - if (hasResults) - return; - - if (this._showValidationMessage(currentElement)) - return; - - // If we're not showing autocomplete suggestions, hide the form assist popup - this._hideFormAssistPopup(); - }; - - this._showAutoCompleteSuggestions(currentElement, checkResultsInput); - break; - } - - // Reset invalid submit state on each pageshow - case "pageshow": { - if (!this._invalidSubmit) - return; - - let selectedBrowser = BrowserApp.selectedBrowser; - if (selectedBrowser) { - let selectedDocument = selectedBrowser.contentDocument; - let target = aEvent.originalTarget; - if (target == selectedDocument || target.ownerDocument == selectedDocument) - this._invalidSubmit = false; - } - break; - } - } - }, - - // We only want to show autocomplete suggestions for certain elements - _isAutoComplete: function _isAutoComplete(aElement) { - if (!(aElement instanceof HTMLInputElement) || aElement.readOnly || aElement.disabled || - (aElement.getAttribute("type") == "password") || - (aElement.hasAttribute("autocomplete") && - aElement.getAttribute("autocomplete").toLowerCase() == "off")) - return false; - - return true; - }, - - // Retrieves autocomplete suggestions for an element from the form autocomplete service. - // aCallback(array_of_suggestions) is called when results are available. - _getAutoCompleteSuggestions: function _getAutoCompleteSuggestions(aSearchString, aElement, aCallback) { - // Cache the form autocomplete service for future use - if (!this._formAutoCompleteService) { - this._formAutoCompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"] - .getService(Ci.nsIFormAutoComplete); - } - - let resultsAvailable = function (results) { - let suggestions = []; - for (let i = 0; i < results.matchCount; i++) { - let value = results.getValueAt(i); - - // Do not show the value if it is the current one in the input field - if (value == aSearchString) - continue; - - // Supply a label and value, since they can differ for datalist suggestions - suggestions.push({ label: value, value: value }); - } - aCallback(suggestions); - }; - - this._formAutoCompleteService.autoCompleteSearchAsync(aElement.name || aElement.id, - aSearchString, aElement, null, - null, resultsAvailable); - }, - - /** - * (Copied from mobile/xul/chrome/content/forms.js) - * This function is similar to getListSuggestions from - * components/satchel/src/nsInputListAutoComplete.js but sadly this one is - * used by the autocomplete.xml binding which is not in used in fennec - */ - _getListSuggestions: function _getListSuggestions(aElement) { - if (!(aElement instanceof HTMLInputElement) || !aElement.list) - return []; - - let suggestions = []; - let filter = !aElement.hasAttribute("mozNoFilter"); - let lowerFieldValue = aElement.value.toLowerCase(); - - let options = aElement.list.options; - let length = options.length; - for (let i = 0; i < length; i++) { - let item = options.item(i); - - let label = item.value; - if (item.label) - label = item.label; - else if (item.text) - label = item.text; - - if (filter && !(label.toLowerCase().includes(lowerFieldValue)) ) - continue; - suggestions.push({ label: label, value: item.value }); - } - - return suggestions; - }, - - // Retrieves autocomplete suggestions for an element from the form autocomplete service - // and sends the suggestions to the Java UI, along with element position data. As - // autocomplete queries are asynchronous, calls aCallback when done with a true - // argument if results were found and false if no results were found. - _showAutoCompleteSuggestions: function _showAutoCompleteSuggestions(aElement, aCallback) { - if (!this._isAutoComplete(aElement)) { - aCallback(false); - return; - } - if (this._isDisabledElement(aElement)) { - aCallback(false); - return; - } - - let isEmpty = (aElement.value.length === 0); - - let resultsAvailable = autoCompleteSuggestions => { - // On desktop, we show datalist suggestions below autocomplete suggestions, - // without duplicates removed. - let listSuggestions = this._getListSuggestions(aElement); - let suggestions = autoCompleteSuggestions.concat(listSuggestions); - - // Return false if there are no suggestions to show - if (!suggestions.length) { - aCallback(false); - return; - } - - Messaging.sendRequest({ - type: "FormAssist:AutoComplete", - suggestions: suggestions, - rect: ElementTouchHelper.getBoundingContentRect(aElement), - isEmpty: isEmpty, - }); - - // Keep track of input element so we can fill it in if the user - // selects an autocomplete suggestion - this._currentInputElement = aElement; - aCallback(true); - }; - - this._getAutoCompleteSuggestions(aElement.value, aElement, resultsAvailable); - }, - - // Only show a validation message if the user submitted an invalid form, - // there's a non-empty message string, and the element is the correct type - _isValidateable: function _isValidateable(aElement) { - if (!this._invalidSubmit || - !aElement.validationMessage || - !(aElement instanceof HTMLInputElement || - aElement instanceof HTMLTextAreaElement || - aElement instanceof HTMLSelectElement || - aElement instanceof HTMLButtonElement)) - return false; - - return true; - }, - - // Sends a validation message and position data for an element to the Java UI. - // Returns true if there's a validation message to show, false otherwise. - _showValidationMessage: function _sendValidationMessage(aElement) { - if (!this._isValidateable(aElement)) - return false; - - Messaging.sendRequest({ - type: "FormAssist:ValidationMessage", - validationMessage: aElement.validationMessage, - rect: ElementTouchHelper.getBoundingContentRect(aElement) - }); - - return true; - }, - - _hideFormAssistPopup: function _hideFormAssistPopup() { - Messaging.sendRequest({ type: "FormAssist:Hide" }); - }, - - _isDisabledElement : function(aElement) { - let currentElement = aElement; - while (currentElement) { - if(currentElement.disabled) - return true; - - currentElement = currentElement.parentElement; - } - return false; - } -}; - -var XPInstallObserver = { - init: function() { - Services.obs.addObserver(this, "addon-install-origin-blocked", false); - Services.obs.addObserver(this, "addon-install-disabled", false); - Services.obs.addObserver(this, "addon-install-blocked", false); - Services.obs.addObserver(this, "addon-install-started", false); - Services.obs.addObserver(this, "xpi-signature-changed", false); - Services.obs.addObserver(this, "browser-delayed-startup-finished", false); - - AddonManager.addInstallListener(this); - }, - - observe: function(aSubject, aTopic, aData) { - let installInfo, tab, host; - if (aSubject && aSubject instanceof Ci.amIWebInstallInfo) { - installInfo = aSubject; - tab = BrowserApp.getTabForBrowser(installInfo.browser); - if (installInfo.originatingURI) { - host = installInfo.originatingURI.host; - } - } - - let strings = Strings.browser; - let brandShortName = Strings.brand.GetStringFromName("brandShortName"); - - switch (aTopic) { - case "addon-install-started": - Snackbars.show(strings.GetStringFromName("alertAddonsDownloading"), Snackbars.LENGTH_LONG); - break; - case "addon-install-disabled": { - if (!tab) - return; - - let enabled = true; - try { - enabled = Services.prefs.getBoolPref("xpinstall.enabled"); - } catch (e) {} - - let buttons, message, callback; - if (!enabled) { - message = strings.GetStringFromName("xpinstallDisabledMessageLocked"); - buttons = [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")]; - callback: (data) => {}; - } else { - message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2); - buttons = [ - strings.GetStringFromName("xpinstallDisabledButton"), - strings.GetStringFromName("unsignedAddonsDisabled.dismiss") - ]; - callback: (data) => { - if (data.button === 1) { - Services.prefs.setBoolPref("xpinstall.enabled", true) - } - }; - } - - new Prompt({ - title: Strings.browser.GetStringFromName("addonError.titleError"), - message: message, - buttons: buttons - }).show(callback); - break; - } - case "addon-install-blocked": { - if (!tab) - return; - - let message; - if (host) { - // We have a host which asked for the install. - message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2); - } else { - // Without a host we address the add-on as the initiator of the install. - let addon = null; - if (installInfo.installs.length > 0) { - addon = installInfo.installs[0].name; - } - if (addon) { - // We have an addon name, show the regular message. - message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2); - } else { - // We don't have an addon name, show an alternative message. - message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1); - } - } - - let buttons = [ - strings.GetStringFromName("xpinstallPromptAllowButton"), - strings.GetStringFromName("unsignedAddonsDisabled.dismiss") - ]; - new Prompt({ - title: Strings.browser.GetStringFromName("addonError.titleBlocked"), - message: message, - buttons: buttons - }).show((data) => { - if (data.button === 0) { - // Kick off the install - installInfo.install(); - } - }); - break; - } - case "addon-install-origin-blocked": { - if (!tab) - return; - - new Prompt({ - title: Strings.browser.GetStringFromName("addonError.titleBlocked"), - message: strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1), - buttons: [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")] - }).show((data) => {}); - break; - } - case "xpi-signature-changed": { - if (JSON.parse(aData).disabled.length) { - this._notifyUnsignedAddonsDisabled(); - } - break; - } - case "browser-delayed-startup-finished": { - let disabledAddons = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED); - for (let id of disabledAddons) { - if (AddonManager.getAddonByID(id).signedState <= AddonManager.SIGNEDSTATE_MISSING) { - this._notifyUnsignedAddonsDisabled(); - break; - } - } - break; - } - } - }, - - _notifyUnsignedAddonsDisabled: function() { - new Prompt({ - window: window, - title: Strings.browser.GetStringFromName("unsignedAddonsDisabled.title"), - message: Strings.browser.GetStringFromName("unsignedAddonsDisabled.message"), - buttons: [ - Strings.browser.GetStringFromName("unsignedAddonsDisabled.viewAddons"), - Strings.browser.GetStringFromName("unsignedAddonsDisabled.dismiss") - ] - }).show((data) => { - if (data.button === 0) { - // TODO: Open about:addons to show only unsigned add-ons? - BrowserApp.selectOrAddTab("about:addons", { parentId: BrowserApp.selectedTab.id }); - } - }); - }, - - onInstallEnded: function(aInstall, aAddon) { - // Don't create a notification for distribution add-ons. - if (Distribution.pendingAddonInstalls.has(aInstall)) { - Distribution.pendingAddonInstalls.delete(aInstall); - return; - } - - let needsRestart = false; - if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE)) - needsRestart = true; - else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) - needsRestart = true; - - if (needsRestart) { - this.showRestartPrompt(); - } else { - // Display completion message for new installs or updates not done Automatically - if (!aInstall.existingAddon || !AddonManager.shouldAutoUpdate(aInstall.existingAddon)) { - let message = Strings.browser.GetStringFromName("alertAddonsInstalledNoRestart.message"); - Snackbars.show(message, Snackbars.LENGTH_LONG, { - action: { - label: Strings.browser.GetStringFromName("alertAddonsInstalledNoRestart.action2"), - callback: () => { - UITelemetry.addEvent("show.1", "toast", null, "addons"); - BrowserApp.selectOrAddTab("about:addons", { parentId: BrowserApp.selectedTab.id }); - }, - } - }); - } - } - }, - - onInstallFailed: function(aInstall) { - this._showErrorMessage(aInstall); - }, - - onDownloadProgress: function(aInstall) {}, - - onDownloadFailed: function(aInstall) { - this._showErrorMessage(aInstall); - }, - - onDownloadCancelled: function(aInstall) {}, - - _showErrorMessage: function(aInstall) { - // Don't create a notification for distribution add-ons. - if (Distribution.pendingAddonInstalls.has(aInstall)) { - Cu.reportError("Error installing distribution add-on: " + aInstall.addon.id); - Distribution.pendingAddonInstalls.delete(aInstall); - return; - } - - let host = (aInstall.originatingURI instanceof Ci.nsIStandardURL) && aInstall.originatingURI.host; - if (!host) { - host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) && aInstall.sourceURI.host; - } - - let error = (host || aInstall.error == 0) ? "addonError" : "addonLocalError"; - if (aInstall.error < 0) { - error += aInstall.error; - } else if (aInstall.addon && aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) { - error += "Blocklisted"; - } else { - error += "Incompatible"; - } - - let msg = Strings.browser.GetStringFromName(error); - // TODO: formatStringFromName - msg = msg.replace("#1", aInstall.name); - if (host) { - msg = msg.replace("#2", host); - } - msg = msg.replace("#3", Strings.brand.GetStringFromName("brandShortName")); - msg = msg.replace("#4", Services.appinfo.version); - - if (aInstall.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) { - new Prompt({ - window: window, - title: Strings.browser.GetStringFromName("addonError.titleBlocked"), - message: msg, - buttons: [Strings.browser.GetStringFromName("addonError.learnMore")] - }).show((data) => { - if (data.button === 0) { - let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"; - BrowserApp.addTab(url, { parentId: BrowserApp.selectedTab.id }); - } - }); - } else { - Services.prompt.alert(null, Strings.browser.GetStringFromName("addonError.titleError"), msg); - } - }, - - showRestartPrompt: function() { - let buttons = [{ - label: Strings.browser.GetStringFromName("notificationRestart.button"), - callback: function() { - // Notify all windows that an application quit has been requested - let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); - Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); - - // If nothing aborted, quit the app - if (cancelQuit.data == false) { - Services.obs.notifyObservers(null, "quit-application-proceeding", null); - let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); - appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); - } - }, - positive: true - }]; - - let message = Strings.browser.GetStringFromName("notificationRestart.normal"); - NativeWindow.doorhanger.show(message, "addon-app-restart", buttons, BrowserApp.selectedTab.id, { persistence: -1 }); - }, - - hideRestartPrompt: function() { - NativeWindow.doorhanger.hide("addon-app-restart", BrowserApp.selectedTab.id); - } -}; - -var ViewportHandler = { - init: function init() { - Services.obs.addObserver(this, "Window:Resize", false); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "Window:Resize" && aData) { - let scrollChange = JSON.parse(aData); - let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - windowUtils.setNextPaintSyncId(scrollChange.id); - } - } -}; - -/** - * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml - */ -var PopupBlockerObserver = { - onUpdatePageReport: function onUpdatePageReport(aEvent) { - let browser = BrowserApp.selectedBrowser; - if (aEvent.originalTarget != browser) - return; - - if (!browser.pageReport) - return; - - let result = Services.perms.testExactPermission(BrowserApp.selectedBrowser.currentURI, "popup"); - if (result == Ci.nsIPermissionManager.DENY_ACTION) - return; - - // Only show the notification again if we've not already shown it. Since - // notifications are per-browser, we don't need to worry about re-adding - // it. - if (!browser.pageReport.reported) { - if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) { - let brandShortName = Strings.brand.GetStringFromName("brandShortName"); - let popupCount = browser.pageReport.length; - - let strings = Strings.browser; - let message = PluralForm.get(popupCount, strings.GetStringFromName("popup.message")) - .replace("#1", brandShortName) - .replace("#2", popupCount); - - let buttons = [ - { - label: strings.GetStringFromName("popup.dontShow"), - callback: function(aChecked) { - if (aChecked) - PopupBlockerObserver.allowPopupsForSite(false); - } - }, - { - label: strings.GetStringFromName("popup.show"), - callback: function(aChecked) { - // Set permission before opening popup windows - if (aChecked) - PopupBlockerObserver.allowPopupsForSite(true); - - PopupBlockerObserver.showPopupsForSite(); - }, - positive: true - } - ]; - - let options = { checkbox: Strings.browser.GetStringFromName("popup.dontAskAgain") }; - NativeWindow.doorhanger.show(message, "popup-blocked", buttons, null, options); - } - // Record the fact that we've reported this blocked popup, so we don't - // show it again. - browser.pageReport.reported = true; - } - }, - - allowPopupsForSite: function allowPopupsForSite(aAllow) { - let currentURI = BrowserApp.selectedBrowser.currentURI; - Services.perms.add(currentURI, "popup", aAllow - ? Ci.nsIPermissionManager.ALLOW_ACTION - : Ci.nsIPermissionManager.DENY_ACTION); - dump("Allowing popups for: " + currentURI); - }, - - showPopupsForSite: function showPopupsForSite() { - let uri = BrowserApp.selectedBrowser.currentURI; - let pageReport = BrowserApp.selectedBrowser.pageReport; - if (pageReport) { - for (let i = 0; i < pageReport.length; ++i) { - let popupURIspec = pageReport[i].popupWindowURIspec; - - // Sometimes the popup URI that we get back from the pageReport - // isn't useful (for instance, netscape.com's popup URI ends up - // being "http://www.netscape.com", which isn't really the URI of - // the popup they're trying to show). This isn't going to be - // useful to the user, so we won't create a menu item for it. - if (popupURIspec == "" || popupURIspec == "about:blank" || popupURIspec == uri.spec) - continue; - - let popupFeatures = pageReport[i].popupWindowFeatures; - let popupName = pageReport[i].popupWindowName; - - let parent = BrowserApp.selectedTab; - let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser); - BrowserApp.addTab(popupURIspec, { parentId: parent.id, isPrivate: isPrivate }); - } - } - } -}; - - -var IndexedDB = { - _permissionsPrompt: "indexedDB-permissions-prompt", - _permissionsResponse: "indexedDB-permissions-response", - - init: function IndexedDB_init() { - Services.obs.addObserver(this, this._permissionsPrompt, false); - }, - - observe: function IndexedDB_observe(subject, topic, data) { - if (topic != this._permissionsPrompt) { - throw new Error("Unexpected topic!"); - } - - let requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor); - - let browser = requestor.getInterface(Ci.nsIDOMNode); - let tab = BrowserApp.getTabForBrowser(browser); - if (!tab) - return; - - let host = browser.currentURI.asciiHost; - - let strings = Strings.browser; - - let message, responseTopic; - if (topic == this._permissionsPrompt) { - message = strings.formatStringFromName("offlineApps.ask", [host], 1); - responseTopic = this._permissionsResponse; - } - - const firstTimeoutDuration = 300000; // 5 minutes - - let timeoutId; - - let notificationID = responseTopic + host; - let observer = requestor.getInterface(Ci.nsIObserver); - - // This will be set to the result of PopupNotifications.show() below, or to - // the result of PopupNotifications.getNotification() if this is a - // quotaCancel notification. - let notification; - - function timeoutNotification() { - // Remove the notification. - NativeWindow.doorhanger.hide(notificationID, tab.id); - - // Clear all of our timeout stuff. We may be called directly, not just - // when the timeout actually elapses. - clearTimeout(timeoutId); - - // And tell the page that the popup timed out. - observer.observe(null, responseTopic, Ci.nsIPermissionManager.UNKNOWN_ACTION); - } - - let buttons = [ - { - label: strings.GetStringFromName("offlineApps.dontAllow2"), - callback: function(aChecked) { - clearTimeout(timeoutId); - let action = aChecked ? Ci.nsIPermissionManager.DENY_ACTION : Ci.nsIPermissionManager.UNKNOWN_ACTION; - observer.observe(null, responseTopic, action); - } - }, - { - label: strings.GetStringFromName("offlineApps.allow"), - callback: function() { - clearTimeout(timeoutId); - observer.observe(null, responseTopic, Ci.nsIPermissionManager.ALLOW_ACTION); - }, - positive: true - }]; - - let options = { checkbox: Strings.browser.GetStringFromName("offlineApps.dontAskAgain") }; - NativeWindow.doorhanger.show(message, notificationID, buttons, tab.id, options); - - // Set the timeoutId after the popup has been created, and use the long - // timeout value. If the user doesn't notice the popup after this amount of - // time then it is most likely not visible and we want to alert the page. - timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration); - } -}; - -var CharacterEncoding = { - _charsets: [], - - init: function init() { - Services.obs.addObserver(this, "CharEncoding:Get", false); - Services.obs.addObserver(this, "CharEncoding:Set", false); - InitLater(() => this.sendState()); - }, - - observe: function observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "CharEncoding:Get": - this.getEncoding(); - break; - case "CharEncoding:Set": - this.setEncoding(aData); - break; - } - }, - - sendState: function sendState() { - let showCharEncoding = "false"; - try { - showCharEncoding = Services.prefs.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data; - } catch (e) { /* Optional */ } - - Messaging.sendRequest({ - type: "CharEncoding:State", - visible: showCharEncoding - }); - }, - - getEncoding: function getEncoding() { - function infoToCharset(info) { - return { code: info.value, title: info.label }; - } - - if (!this._charsets.length) { - let data = CharsetMenu.getData(); - - // In the desktop UI, the pinned charsets are shown above the rest. - let pinnedCharsets = data.pinnedCharsets.map(infoToCharset); - let otherCharsets = data.otherCharsets.map(infoToCharset) - - this._charsets = pinnedCharsets.concat(otherCharsets); - } - - // Look for the index of the selected charset. Default to -1 if the - // doc charset isn't found in the list of available charsets. - let docCharset = BrowserApp.selectedBrowser.contentDocument.characterSet; - let selected = -1; - let charsetCount = this._charsets.length; - - for (let i = 0; i < charsetCount; i++) { - if (this._charsets[i].code === docCharset) { - selected = i; - break; - } - } - - Messaging.sendRequest({ - type: "CharEncoding:Data", - charsets: this._charsets, - selected: selected - }); - }, - - setEncoding: function setEncoding(aEncoding) { - let browser = BrowserApp.selectedBrowser; - browser.docShell.gatherCharsetMenuTelemetry(); - browser.docShell.charset = aEncoding; - browser.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); - } -}; - -var IdentityHandler = { - // No trusted identity information. No site identity icon is shown. - IDENTITY_MODE_UNKNOWN: "unknown", - - // Domain-Validation SSL CA-signed domain verification (DV). - IDENTITY_MODE_IDENTIFIED: "identified", - - // Extended-Validation SSL CA-signed identity information (EV). A more rigorous validation process. - IDENTITY_MODE_VERIFIED: "verified", - - // Part of the product's UI (built in about: pages) - IDENTITY_MODE_CHROMEUI: "chromeUI", - - // The following mixed content modes are only used if "security.mixed_content.block_active_content" - // is enabled. Our Java frontend coalesces them into one indicator. - - // No mixed content information. No mixed content icon is shown. - MIXED_MODE_UNKNOWN: "unknown", - - // Blocked active mixed content. - MIXED_MODE_CONTENT_BLOCKED: "blocked", - - // Loaded active mixed content. - MIXED_MODE_CONTENT_LOADED: "loaded", - - // The following tracking content modes are only used if tracking protection - // is enabled. Our Java frontend coalesces them into one indicator. - - // No tracking content information. No tracking content icon is shown. - TRACKING_MODE_UNKNOWN: "unknown", - - // Blocked active tracking content. Shield icon is shown, with a popup option to load content. - TRACKING_MODE_CONTENT_BLOCKED: "tracking_content_blocked", - - // Loaded active tracking content. Yellow triangle icon is shown. - TRACKING_MODE_CONTENT_LOADED: "tracking_content_loaded", - - // Cache the most recent SSLStatus and Location seen in getIdentityStrings - _lastStatus : null, - _lastLocation : null, - - /** - * Helper to parse out the important parts of _lastStatus (of the SSL cert in - * particular) for use in constructing identity UI strings - */ - getIdentityData : function() { - let result = {}; - let status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus); - let cert = status.serverCert; - - // Human readable name of Subject - result.subjectOrg = cert.organization; - - // SubjectName fields, broken up for individual access - if (cert.subjectName) { - result.subjectNameFields = {}; - cert.subjectName.split(",").forEach(function(v) { - let field = v.split("="); - this[field[0]] = field[1]; - }, result.subjectNameFields); - - // Call out city, state, and country specifically - result.city = result.subjectNameFields.L; - result.state = result.subjectNameFields.ST; - result.country = result.subjectNameFields.C; - } - - // Human readable name of Certificate Authority - result.caOrg = cert.issuerOrganization || cert.issuerCommonName; - result.cert = cert; - - return result; - }, - - /** - * Determines the identity mode corresponding to the icon we show in the urlbar. - */ - getIdentityMode: function getIdentityMode(aState, uri) { - if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) { - return this.IDENTITY_MODE_VERIFIED; - } - - if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) { - return this.IDENTITY_MODE_IDENTIFIED; - } - - // We also allow "about:" by allowing the selector to be empty (i.e. '(|.....|...|...)' - let whitelist = /^about:($|about|accounts|addons|buildconfig|cache|config|crashes|devices|downloads|fennec|firefox|feedback|healthreport|home|license|logins|logo|memory|mozilla|networking|plugins|privatebrowsing|rights|serviceworkers|support|telemetry|webrtc)($|\?)/i; - if (uri.schemeIs("about") && whitelist.test(uri.spec)) { - return this.IDENTITY_MODE_CHROMEUI; - } - - return this.IDENTITY_MODE_UNKNOWN; - }, - - getMixedDisplayMode: function getMixedDisplayMode(aState) { - if (aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT) { - return this.MIXED_MODE_CONTENT_LOADED; - } - - if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT) { - return this.MIXED_MODE_CONTENT_BLOCKED; - } - - return this.MIXED_MODE_UNKNOWN; - }, - - getMixedActiveMode: function getActiveDisplayMode(aState) { - // Only show an indicator for loaded mixed content if the pref to block it is enabled - if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) && - !Services.prefs.getBoolPref("security.mixed_content.block_active_content")) { - return this.MIXED_MODE_CONTENT_LOADED; - } - - if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) { - return this.MIXED_MODE_CONTENT_BLOCKED; - } - - return this.MIXED_MODE_UNKNOWN; - }, - - getTrackingMode: function getTrackingMode(aState, aBrowser) { - if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) { - this.shieldHistogramAdd(aBrowser, 2); - return this.TRACKING_MODE_CONTENT_BLOCKED; - } - - // Only show an indicator for loaded tracking content if the pref to block it is enabled - let tpEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || - (Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled") && - PrivateBrowsingUtils.isBrowserPrivate(aBrowser)); - - if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) && tpEnabled) { - this.shieldHistogramAdd(aBrowser, 1); - return this.TRACKING_MODE_CONTENT_LOADED; - } - - this.shieldHistogramAdd(aBrowser, 0); - return this.TRACKING_MODE_UNKNOWN; - }, - - shieldHistogramAdd: function(browser, value) { - if (PrivateBrowsingUtils.isBrowserPrivate(browser)) { - return; - } - Telemetry.addData("TRACKING_PROTECTION_SHIELD", value); - }, - - /** - * Determine the identity of the page being displayed by examining its SSL cert - * (if available). Return the data needed to update the UI. - */ - checkIdentity: function checkIdentity(aState, aBrowser) { - this._lastStatus = aBrowser.securityUI - .QueryInterface(Components.interfaces.nsISSLStatusProvider) - .SSLStatus; - - // Don't pass in the actual location object, since it can cause us to - // hold on to the window object too long. Just pass in the fields we - // care about. (bug 424829) - let locationObj = {}; - try { - let location = aBrowser.contentWindow.location; - locationObj.host = location.host; - locationObj.hostname = location.hostname; - locationObj.port = location.port; - locationObj.origin = location.origin; - } catch (ex) { - // Can sometimes throw if the URL being visited has no host/hostname, - // e.g. about:blank. The _state for these pages means we won't need these - // properties anyways, though. - } - this._lastLocation = locationObj; - - let uri = aBrowser.currentURI; - try { - uri = Services.uriFixup.createExposableURI(uri); - } catch (e) {} - - let identityMode = this.getIdentityMode(aState, uri); - let mixedDisplay = this.getMixedDisplayMode(aState); - let mixedActive = this.getMixedActiveMode(aState); - let trackingMode = this.getTrackingMode(aState, aBrowser); - let result = { - origin: locationObj.origin, - mode: { - identity: identityMode, - mixed_display: mixedDisplay, - mixed_active: mixedActive, - tracking: trackingMode - } - }; - - // Don't show identity data for pages with an unknown identity or if any - // mixed content is loaded (mixed display content is loaded by default). - // We also return for CHROMEUI pages since they don't have any certificate - // information to load either. result.secure specifically refers to connection - // security, which is irrelevant for about: pages, as they're loaded locally. - if (identityMode == this.IDENTITY_MODE_UNKNOWN || - identityMode == this.IDENTITY_MODE_CHROMEUI || - aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) { - result.secure = false; - return result; - } - - result.secure = true; - - result.host = this.getEffectiveHost(); - - let iData = this.getIdentityData(); - result.verifier = Strings.browser.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1); - - // If the cert is identified, then we can populate the results with credentials - if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) { - result.owner = iData.subjectOrg; - - // Build an appropriate supplemental block out of whatever location data we have - let supplemental = ""; - if (iData.city) { - supplemental += iData.city + "\n"; - } - if (iData.state && iData.country) { - supplemental += Strings.browser.formatStringFromName("identity.identified.state_and_country", [iData.state, iData.country], 2); - result.country = iData.country; - } else if (iData.state) { // State only - supplemental += iData.state; - } else if (iData.country) { // Country only - supplemental += iData.country; - result.country = iData.country; - } - result.supplemental = supplemental; - - return result; - } - - // Cache the override service the first time we need to check it - if (!this._overrideService) - this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService); - - // Check whether this site is a security exception. XPConnect does the right - // thing here in terms of converting _lastLocation.port from string to int, but - // the overrideService doesn't like undefined ports, so make sure we have - // something in the default case (bug 432241). - // .hostname can return an empty string in some exceptional cases - - // hasMatchingOverride does not handle that, so avoid calling it. - // Updating the tooltip value in those cases isn't critical. - // FIXME: Fixing bug 646690 would probably makes this check unnecessary - if (this._lastLocation.hostname && - this._overrideService.hasMatchingOverride(this._lastLocation.hostname, - (this._lastLocation.port || 443), - iData.cert, {}, {})) - result.verifier = Strings.browser.GetStringFromName("identity.identified.verified_by_you"); - - return result; - }, - - /** - * Attempt to provide proper IDN treatment for host names - */ - getEffectiveHost: function getEffectiveHost() { - if (!this._IDNService) - this._IDNService = Cc["@mozilla.org/network/idn-service;1"] - .getService(Ci.nsIIDNService); - try { - return this._IDNService.convertToDisplayIDN(this._uri.host, {}); - } catch (e) { - // If something goes wrong (e.g. hostname is an IP address) just fail back - // to the full domain. - return this._lastLocation.hostname; - } - } -}; - -var SearchEngines = { - _contextMenuId: null, - PREF_SUGGEST_ENABLED: "browser.search.suggest.enabled", - PREF_SUGGEST_PROMPTED: "browser.search.suggest.prompted", - - // Shared preference key used for search activity default engine. - PREF_SEARCH_ACTIVITY_ENGINE_KEY: "search.engines.defaultname", - - init: function init() { - Services.obs.addObserver(this, "SearchEngines:Add", false); - Services.obs.addObserver(this, "SearchEngines:GetVisible", false); - Services.obs.addObserver(this, "SearchEngines:Remove", false); - Services.obs.addObserver(this, "SearchEngines:RestoreDefaults", false); - Services.obs.addObserver(this, "SearchEngines:SetDefault", false); - Services.obs.addObserver(this, "browser-search-engine-modified", false); - }, - - // Fetch list of search engines. all ? All engines : Visible engines only. - _handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv, all) { - if (!Components.isSuccessCode(rv)) { - Cu.reportError("Could not initialize search service, bailing out."); - return; - } - - let engineData = Services.search.getVisibleEngines({}); - - // Our Java UI assumes that the default engine is the first item in the array, - // so we need to make sure that's the case. - if (engineData[0] !== Services.search.defaultEngine) { - engineData = engineData.filter(engine => engine !== Services.search.defaultEngine); - engineData.unshift(Services.search.defaultEngine); - } - - let searchEngines = engineData.map(function (engine) { - return { - name: engine.name, - identifier: engine.identifier, - iconURI: (engine.iconURI ? engine.iconURI.spec : null), - hidden: engine.hidden - }; - }); - - let suggestTemplate = null; - let suggestEngine = null; - - // Check to see if the default engine supports search suggestions. We only need to check - // the default engine because we only show suggestions for the default engine in the UI. - let engine = Services.search.defaultEngine; - if (engine.supportsResponseType("application/x-suggestions+json")) { - suggestEngine = engine.name; - suggestTemplate = engine.getSubmission("__searchTerms__", "application/x-suggestions+json").uri.spec; - } - - // By convention, the currently configured default engine is at position zero in searchEngines. - Messaging.sendRequest({ - type: "SearchEngines:Data", - searchEngines: searchEngines, - suggest: { - engine: suggestEngine, - template: suggestTemplate, - enabled: Services.prefs.getBoolPref(this.PREF_SUGGEST_ENABLED), - prompted: Services.prefs.getBoolPref(this.PREF_SUGGEST_PROMPTED) - } - }); - - // Send a speculative connection to the default engine. - Services.search.defaultEngine.speculativeConnect({window: window}); - }, - - // Helper method to extract the engine name from a JSON. Simplifies the observe function. - _extractEngineFromJSON: function _extractEngineFromJSON(aData) { - let data = JSON.parse(aData); - return Services.search.getEngineByName(data.engine); - }, - - observe: function observe(aSubject, aTopic, aData) { - let engine; - switch(aTopic) { - case "SearchEngines:Add": - this.displaySearchEnginesList(aData); - break; - case "SearchEngines:GetVisible": - Services.search.init(this._handleSearchEnginesGetVisible.bind(this)); - break; - case "SearchEngines:Remove": - // Make sure the engine isn't hidden before removing it, to make sure it's - // visible if the user later re-adds it (works around bug 341833) - engine = this._extractEngineFromJSON(aData); - engine.hidden = false; - Services.search.removeEngine(engine); - break; - case "SearchEngines:RestoreDefaults": - // Un-hides all default engines. - Services.search.restoreDefaultEngines(); - break; - case "SearchEngines:SetDefault": - engine = this._extractEngineFromJSON(aData); - // Move the new default search engine to the top of the search engine list. - Services.search.moveEngine(engine, 0); - Services.search.defaultEngine = engine; - break; - case "browser-search-engine-modified": - if (aData == "engine-default") { - this._setSearchActivityDefaultPref(aSubject.QueryInterface(Ci.nsISearchEngine)); - } - break; - default: - dump("Unexpected message type observed: " + aTopic); - break; - } - }, - - migrateSearchActivityDefaultPref: function migrateSearchActivityDefaultPref() { - Services.search.init(() => this._setSearchActivityDefaultPref(Services.search.defaultEngine)); - }, - - // Updates the search activity pref when the default engine changes. - _setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) { - SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, engine.name); - }, - - // Display context menu listing names of the search engines available to be added. - displaySearchEnginesList: function displaySearchEnginesList(aData) { - let data = JSON.parse(aData); - let tab = BrowserApp.getTabForId(data.tabId); - - if (!tab) - return; - - let browser = tab.browser; - let engines = browser.engines; - - let p = new Prompt({ - window: browser.contentWindow - }).setSingleChoiceItems(engines.map(function(e) { - return { label: e.title }; - })).show((function(data) { - if (data.button == -1) - return; - - this.addOpenSearchEngine(engines[data.button]); - engines.splice(data.button, 1); - - if (engines.length < 1) { - // Broadcast message that there are no more add-able search engines. - let newEngineMessage = { - type: "Link:OpenSearch", - tabID: tab.id, - visible: false - }; - - Messaging.sendRequest(newEngineMessage); - } - }).bind(this)); - }, - - addOpenSearchEngine: function addOpenSearchEngine(engine) { - Services.search.addEngine(engine.url, Ci.nsISearchEngine.DATA_XML, engine.iconURL, false, { - onSuccess: function() { - // Display a toast confirming addition of new search engine. - Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [engine.title], 1), Snackbars.LENGTH_LONG); - }, - - onError: function(aCode) { - let errorMessage; - if (aCode == 2) { - // Engine is a duplicate. - errorMessage = "alertSearchEngineDuplicateToast"; - - } else { - // Unknown failure. Display general error message. - errorMessage = "alertSearchEngineErrorToast"; - } - - Snackbars.show(Strings.browser.formatStringFromName(errorMessage, [engine.title], 1), Snackbars.LENGTH_LONG); - } - }); - }, - - /** - * Build and return an array of sorted form data / Query Parameters - * for an element in a submission form. - * - * @param element - * A valid submission element of a form. - */ - _getSortedFormData: function(element) { - let formData = []; - - for (let formElement of element.form.elements) { - if (!formElement.type) { - continue; - } - - // Make this text field a generic search parameter. - if (element == formElement) { - formData.push({ name: formElement.name, value: "{searchTerms}" }); - continue; - } - - // Add other form elements as parameters. - switch (formElement.type.toLowerCase()) { - case "checkbox": - case "radio": - if (!formElement.checked) { - break; - } - case "text": - case "hidden": - case "textarea": - formData.push({ name: escape(formElement.name), value: escape(formElement.value) }); - break; - - case "select-one": { - for (let option of formElement.options) { - if (option.selected) { - formData.push({ name: escape(formElement.name), value: escape(formElement.value) }); - break; - } - } - } - } - }; - - // Return valid, pre-sorted queryParams. - return formData.filter(a => a.name && a.value).sort((a, b) => { - // nsIBrowserSearchService.hasEngineWithURL() ensures sort, but this helps. - if (a.name > b.name) { - return 1; - } - if (b.name > a.name) { - return -1; - } - - if (a.value > b.value) { - return 1; - } - if (b.value > a.value) { - return -1; - } - - return 0; - }); - }, - - /** - * Check if any search engines already handle an EngineURL of type - * URLTYPE_SEARCH_HTML, matching this request-method, formURL, and queryParams. - */ - visibleEngineExists: function(element) { - let formData = this._getSortedFormData(element); - - let form = element.form; - let method = form.method.toUpperCase(); - - let charset = element.ownerDocument.characterSet; - let docURI = Services.io.newURI(element.ownerDocument.URL, charset, null); - let formURL = Services.io.newURI(form.getAttribute("action"), charset, docURI).spec; - - return Services.search.hasEngineWithURL(method, formURL, formData); - }, - - /** - * Adds a new search engine to the BrowserSearchService, based on its provided element. Prompts for an engine - * name, and appends a simple version-number in case of collision with an existing name. - * - * @return callback to handle success value. Currently used for ActionBarHandler.js and UI updates. - */ - addEngine: function addEngine(aElement, resultCallback) { - let form = aElement.form; - let charset = aElement.ownerDocument.characterSet; - let docURI = Services.io.newURI(aElement.ownerDocument.URL, charset, null); - let formURL = Services.io.newURI(form.getAttribute("action"), charset, docURI).spec; - let method = form.method.toUpperCase(); - let formData = this._getSortedFormData(aElement); - - // prompt user for name of search engine - let promptTitle = Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"); - let title = { value: (aElement.ownerDocument.title || docURI.host) }; - if (!Services.prompt.prompt(null, promptTitle, null, title, null, {})) { - if (resultCallback) { - resultCallback(false); - }; - return; - } - - // fetch the favicon for this page - let dbFile = FileUtils.getFile("ProfD", ["browser.db"]); - let mDBConn = Services.storage.openDatabase(dbFile); - let stmts = []; - stmts[0] = mDBConn.createStatement("SELECT favicon FROM history_with_favicons WHERE url = ?"); - stmts[0].bindByIndex(0, docURI.spec); - let favicon = null; - - Services.search.init(function addEngine_cb(rv) { - if (!Components.isSuccessCode(rv)) { - Cu.reportError("Could not initialize search service, bailing out."); - if (resultCallback) { - resultCallback(false); - }; - return; - } - - mDBConn.executeAsync(stmts, stmts.length, { - handleResult: function (results) { - let bytes = results.getNextRow().getResultByName("favicon"); - if (bytes && bytes.length) { - favicon = "data:image/x-icon;base64," + btoa(String.fromCharCode.apply(null, bytes)); - } - }, - handleCompletion: function (reason) { - // if there's already an engine with this name, add a number to - // make the name unique (e.g., "Google" becomes "Google 2") - let name = title.value; - for (let i = 2; Services.search.getEngineByName(name); i++) - name = title.value + " " + i; - - Services.search.addEngineWithDetails(name, favicon, null, null, method, formURL); - Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [name], 1), Snackbars.LENGTH_LONG); - - let engine = Services.search.getEngineByName(name); - engine.wrappedJSObject._queryCharset = charset; - formData.forEach(param => { engine.addParam(param.name, param.value, null); }); - - if (resultCallback) { - return resultCallback(true); - }; - } - }); - }); - } -}; - -var ActivityObserver = { - init: function ao_init() { - Services.obs.addObserver(this, "application-background", false); - Services.obs.addObserver(this, "application-foreground", false); - }, - - observe: function ao_observe(aSubject, aTopic, aData) { - let isForeground = false; - let tab = BrowserApp.selectedTab; - - UITelemetry.addEvent("show.1", "system", null, aTopic); - - switch (aTopic) { - case "application-background" : - let doc = (tab ? tab.browser.contentDocument : null); - if (doc && doc.fullscreenElement) { - doc.exitFullscreen(); - } - isForeground = false; - break; - case "application-foreground" : - isForeground = true; - break; - } - - if (tab && tab.getActive() != isForeground) { - tab.setActive(isForeground); - } - } -}; - -var Telemetry = { - addData: function addData(aHistogramId, aValue) { - let histogram = Services.telemetry.getHistogramById(aHistogramId); - histogram.add(aValue); - }, -}; - -var Experiments = { - // Enable malware download protection (bug 936041) - MALWARE_DOWNLOAD_PROTECTION: "malware-download-protection", - - // Try to load pages from disk cache when network is offline (bug 935190) - OFFLINE_CACHE: "offline-cache", - - init() { - Messaging.sendRequestForResult({ - type: "Experiments:GetActive" - }).then(experiments => { - let names = JSON.parse(experiments); - for (let name of names) { - switch (name) { - case this.MALWARE_DOWNLOAD_PROTECTION: { - // Apply experiment preferences on the default branch. This allows - // us to avoid migrating user prefs when experiments are enabled/disabled, - // and it also allows users to override these prefs in about:config. - let defaults = Services.prefs.getDefaultBranch(null); - defaults.setBoolPref("browser.safebrowsing.downloads.enabled", true); - defaults.setBoolPref("browser.safebrowsing.downloads.remote.enabled", true); - continue; - } - - case this.OFFLINE_CACHE: { - let defaults = Services.prefs.getDefaultBranch(null); - defaults.setBoolPref("browser.tabs.useCache", true); - continue; - } - } - } - }); - }, - - setOverride(name, isEnabled) { - Messaging.sendRequest({ - type: "Experiments:SetOverride", - name: name, - isEnabled: isEnabled - }); - }, - - clearOverride(name) { - Messaging.sendRequest({ - type: "Experiments:ClearOverride", - name: name - }); - } -}; - -var ExternalApps = { - _contextMenuId: null, - - // extend _getLink to pickup html5 media links. - _getMediaLink: function(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); - if (uri == null && aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && (aElement instanceof Ci.nsIDOMHTMLMediaElement)) { - try { - let mediaSrc = aElement.currentSrc || aElement.src; - uri = ContentAreaUtils.makeURI(mediaSrc, null, null); - } catch (e) {} - } - return uri; - }, - - init: function helper_init() { - this._contextMenuId = NativeWindow.contextmenus.add(function(aElement) { - let uri = null; - var node = aElement; - while (node && !uri) { - uri = ExternalApps._getMediaLink(node); - node = node.parentNode; - } - let apps = []; - if (uri) - apps = HelperApps.getAppsForUri(uri); - - return apps.length == 1 ? Strings.browser.formatStringFromName("helperapps.openWithApp2", [apps[0].name], 1) : - Strings.browser.GetStringFromName("helperapps.openWithList2"); - }, this.filter, this.openExternal); - }, - - filter: { - matches: function(aElement) { - let uri = ExternalApps._getMediaLink(aElement); - let apps = []; - if (uri) { - apps = HelperApps.getAppsForUri(uri); - } - return apps.length > 0; - } - }, - - openExternal: function(aElement) { - if (aElement.pause) { - aElement.pause(); - } - let uri = ExternalApps._getMediaLink(aElement); - HelperApps.launchUri(uri); - }, - - shouldCheckUri: function(uri) { - if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file"))) { - return false; - } - - return true; - }, - - updatePageAction: function updatePageAction(uri, contentDocument) { - HelperApps.getAppsForUri(uri, { filterHttp: true }, (apps) => { - this.clearPageAction(); - if (apps.length > 0) - this._setUriForPageAction(uri, apps, contentDocument); - }); - }, - - updatePageActionUri: function updatePageActionUri(uri) { - this._pageActionUri = uri; - }, - - _getMediaContentElement(contentDocument) { - if (!contentDocument.contentType.startsWith("video/") && - !contentDocument.contentType.startsWith("audio/")) { - return null; - } - - let element = contentDocument.activeElement; - - if (element instanceof HTMLBodyElement) { - element = element.firstChild; - } - - if (element instanceof HTMLMediaElement) { - return element; - } - - return null; - }, - - _setUriForPageAction: function setUriForPageAction(uri, apps, contentDocument) { - this.updatePageActionUri(uri); - - // If the pageaction is already added, simply update the URI to be launched when 'onclick' is triggered. - if (this._pageActionId != undefined) - return; - - let mediaElement = this._getMediaContentElement(contentDocument); - - this._pageActionId = PageActions.add({ - title: Strings.browser.GetStringFromName("openInApp.pageAction"), - icon: "drawable://icon_openinapp", - - clickCallback: () => { - UITelemetry.addEvent("launch.1", "pageaction", null, "helper"); - - let wasPlaying = mediaElement && !mediaElement.paused && !mediaElement.ended; - if (wasPlaying) { - mediaElement.pause(); - } - - if (apps.length > 1) { - // Use the HelperApps prompt here to filter out any Http handlers - HelperApps.prompt(apps, { - title: Strings.browser.GetStringFromName("openInApp.pageAction"), - buttons: [ - Strings.browser.GetStringFromName("openInApp.ok"), - Strings.browser.GetStringFromName("openInApp.cancel") - ] - }, (result) => { - if (result.button != 0) { - if (wasPlaying) { - mediaElement.play(); - } - - return; - } - apps[result.icongrid0].launch(this._pageActionUri); - }); - } else { - apps[0].launch(this._pageActionUri); - } - } - }); - }, - - clearPageAction: function clearPageAction() { - if(!this._pageActionId) - return; - - PageActions.remove(this._pageActionId); - delete this._pageActionId; - }, -}; - -var Distribution = { - // File used to store campaign data - _file: null, - - _preferencesJSON: null, - - init: function dc_init() { - Services.obs.addObserver(this, "Distribution:Changed", false); - Services.obs.addObserver(this, "Distribution:Set", false); - Services.obs.addObserver(this, "prefservice:after-app-defaults", false); - Services.obs.addObserver(this, "Campaign:Set", false); - - // Look for file outside the APK: - // /data/data/org.mozilla.xxx/distribution.json - this._file = Services.dirsvc.get("XCurProcD", Ci.nsIFile); - this._file.append("distribution.json"); - this.readJSON(this._file, this.update); - }, - - observe: function dc_observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "Distribution:Changed": - // Re-init the search service. - try { - Services.search._asyncReInit(); - } catch (e) { - console.log("Unable to reinit search service."); - } - // Fall through. - - case "Distribution:Set": - if (aData) { - try { - this._preferencesJSON = JSON.parse(aData); - } catch (e) { - console.log("Invalid distribution JSON."); - } - } - // Reload the default prefs so we can observe "prefservice:after-app-defaults" - Services.prefs.QueryInterface(Ci.nsIObserver).observe(null, "reload-default-prefs", null); - this.installDistroAddons(); - break; - - case "prefservice:after-app-defaults": - this.getPrefs(); - break; - - case "Campaign:Set": { - // Update the prefs for this session - try { - this.update(JSON.parse(aData)); - } catch (ex) { - Cu.reportError("Distribution: Could not parse JSON: " + ex); - return; - } - - // Asynchronously copy the data to the file. - let array = new TextEncoder().encode(aData); - OS.File.writeAtomic(this._file.path, array, { tmpPath: this._file.path + ".tmp" }); - break; - } - } - }, - - update: function dc_update(aData) { - // Force the distribution preferences on the default branch - let defaults = Services.prefs.getDefaultBranch(null); - defaults.setCharPref("distribution.id", aData.id); - defaults.setCharPref("distribution.version", aData.version); - }, - - getPrefs: function dc_getPrefs() { - if (this._preferencesJSON) { - this.applyPrefs(this._preferencesJSON); - this._preferencesJSON = null; - return; - } - - // Get the distribution directory, and bail if it doesn't exist. - let file = FileUtils.getDir("XREAppDist", [], false); - if (!file.exists()) - return; - - file.append("preferences.json"); - this.readJSON(file, this.applyPrefs); - }, - - applyPrefs: function dc_applyPrefs(aData) { - // Check for required Global preferences - let global = aData["Global"]; - if (!(global && global["id"] && global["version"] && global["about"])) { - Cu.reportError("Distribution: missing or incomplete Global preferences"); - return; - } - - // Force the distribution preferences on the default branch - let defaults = Services.prefs.getDefaultBranch(null); - defaults.setCharPref("distribution.id", global["id"]); - defaults.setCharPref("distribution.version", global["version"]); - - let locale = BrowserApp.getUALocalePref(); - let aboutString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); - aboutString.data = global["about." + locale] || global["about"]; - defaults.setComplexValue("distribution.about", Ci.nsISupportsString, aboutString); - - let prefs = aData["Preferences"]; - for (let key in prefs) { - try { - let value = prefs[key]; - switch (typeof value) { - case "boolean": - defaults.setBoolPref(key, value); - break; - case "number": - defaults.setIntPref(key, value); - break; - case "string": - case "undefined": - defaults.setCharPref(key, value); - break; - } - } catch (e) { /* ignore bad prefs and move on */ } - } - - // Apply a lightweight theme if necessary - if (prefs && prefs["lightweightThemes.selectedThemeID"]) { - Services.obs.notifyObservers(null, "lightweight-theme-apply", ""); - } - - let localizedString = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(Ci.nsIPrefLocalizedString); - let localizeablePrefs = aData["LocalizablePreferences"]; - for (let key in localizeablePrefs) { - try { - let value = localizeablePrefs[key]; - value = value.replace(/%LOCALE%/g, locale); - localizedString.data = "data:text/plain," + key + "=" + value; - defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString); - } catch (e) { /* ignore bad prefs and move on */ } - } - - let localizeablePrefsOverrides = aData["LocalizablePreferences." + locale]; - for (let key in localizeablePrefsOverrides) { - try { - let value = localizeablePrefsOverrides[key]; - localizedString.data = "data:text/plain," + key + "=" + value; - defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString); - } catch (e) { /* ignore bad prefs and move on */ } - } - - Messaging.sendRequest({ type: "Distribution:Set:OK" }); - }, - - // aFile is an nsIFile - // aCallback takes the parsed JSON object as a parameter - readJSON: function dc_readJSON(aFile, aCallback) { - Task.spawn(function() { - let bytes = yield OS.File.read(aFile.path); - let raw = new TextDecoder().decode(bytes) || ""; - - try { - aCallback(JSON.parse(raw)); - } catch (e) { - Cu.reportError("Distribution: Could not parse JSON: " + e); - } - }).then(null, function onError(reason) { - if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) { - Cu.reportError("Distribution: Could not read from " + aFile.leafName + " file"); - } - }); - }, - - // Track pending installs so we can avoid showing notifications for them. - pendingAddonInstalls: new Set(), - - installDistroAddons: Task.async(function* () { - const PREF_ADDONS_INSTALLED = "distribution.addonsInstalled"; - try { - let installed = Services.prefs.getBoolPref(PREF_ADDONS_INSTALLED); - if (installed) { - return; - } - } catch (e) { - Services.prefs.setBoolPref(PREF_ADDONS_INSTALLED, true); - } - - let distroPath; - try { - distroPath = FileUtils.getDir("XREAppDist", ["extensions"]).path; - - let info = yield OS.File.stat(distroPath); - if (!info.isDir) { - return; - } - } catch (e) { - return; - } - - let it = new OS.File.DirectoryIterator(distroPath); - try { - yield it.forEach(entry => { - // Only support extensions that are zipped in .xpi files. - if (entry.isDir || !entry.name.endsWith(".xpi")) { - dump("Ignoring distribution add-on that isn't an XPI: " + entry.path); - return; - } - - new Promise((resolve, reject) => { - AddonManager.getInstallForFile(new FileUtils.File(entry.path), resolve); - }).then(install => { - let id = entry.name.substring(0, entry.name.length - 4); - if (install.addon.id !== id) { - Cu.reportError("File entry " + entry.path + " contains an add-on with an incorrect ID"); - return; - } - this.pendingAddonInstalls.add(install); - install.install(); - }).catch(e => { - Cu.reportError("Error installing distribution add-on: " + entry.path + ": " + e); - }); - }); - } finally { - it.close(); - } - }) -}; - -var Tabs = { - _enableTabExpiration: false, - _useCache: false, - _domains: new Set(), - - init: function() { - // On low-memory platforms, always allow tab expiration. On high-mem - // platforms, allow it to be turned on once we hit a low-mem situation. - if (BrowserApp.isOnLowMemoryPlatform) { - this._enableTabExpiration = true; - } else { - Services.obs.addObserver(this, "memory-pressure", false); - } - - // Watch for opportunities to pre-connect to high probability targets. - Services.obs.addObserver(this, "Session:Prefetch", false); - - // Track the network connection so we can efficiently use the cache - // for possible offline rendering. - Services.obs.addObserver(this, "network:link-status-changed", false); - let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService); - this.useCache = !network.isLinkUp; - - BrowserApp.deck.addEventListener("pageshow", this, false); - BrowserApp.deck.addEventListener("TabOpen", this, false); - }, - - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "memory-pressure": - if (aData != "heap-minimize") { - // We received a low-memory related notification. This will enable - // expirations. - this._enableTabExpiration = true; - Services.obs.removeObserver(this, "memory-pressure"); - } else { - // Use "heap-minimize" as a trigger to expire the most stale tab. - this.expireLruTab(); - } - break; - case "Session:Prefetch": - if (aData) { - try { - let uri = Services.io.newURI(aData, null, null); - if (uri && !this._domains.has(uri.host)) { - Services.io.QueryInterface(Ci.nsISpeculativeConnect).speculativeConnect(uri, null); - this._domains.add(uri.host); - } - } catch (e) {} - } - break; - case "network:link-status-changed": - if (["down", "unknown", "up"].indexOf(aData) == -1) { - return; - } - this.useCache = (aData === "down"); - break; - } - }, - - handleEvent: function(aEvent) { - switch (aEvent.type) { - case "pageshow": - // Clear the domain cache whenever a page is loaded into any browser. - this._domains.clear(); - - break; - case "TabOpen": - // Use opening a new tab as a trigger to expire the most stale tab. - this.expireLruTab(); - break; - } - }, - - // Manage the most-recently-used list of tabs. Each tab has a timestamp - // associated with it that indicates when it was last touched. - expireLruTab: function() { - if (!this._enableTabExpiration) { - return false; - } - let expireTimeMs = Services.prefs.getIntPref("browser.tabs.expireTime") * 1000; - if (expireTimeMs < 0) { - // This behaviour is disabled. - return false; - } - let tabs = BrowserApp.tabs; - let selected = BrowserApp.selectedTab; - let lruTab = null; - // Find the least recently used non-zombie tab. - for (let i = 0; i < tabs.length; i++) { - if (tabs[i] == selected || - tabs[i].browser.__SS_restore || - tabs[i].playingAudio) { - // This tab is selected, is already a zombie, or is currently playing - // audio, skip it. - continue; - } - if (lruTab == null || tabs[i].lastTouchedAt < lruTab.lastTouchedAt) { - lruTab = tabs[i]; - } - } - // If the tab was last touched more than browser.tabs.expireTime seconds ago, - // zombify it. - if (lruTab) { - if (Date.now() - lruTab.lastTouchedAt > expireTimeMs) { - MemoryObserver.zombify(lruTab); - return true; - } - } - return false; - }, - - get useCache() { - if (!Services.prefs.getBoolPref("browser.tabs.useCache")) { - return false; - } - return this._useCache; - }, - - set useCache(aUseCache) { - if (!Services.prefs.getBoolPref("browser.tabs.useCache")) { - return; - } - - if (this._useCache == aUseCache) { - return; - } - - BrowserApp.tabs.forEach(function(tab) { - if (tab.browser && tab.browser.docShell) { - if (aUseCache) { - tab.browser.docShell.defaultLoadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE; - } else { - tab.browser.docShell.defaultLoadFlags &= ~Ci.nsIRequest.LOAD_FROM_CACHE; - } - } - }); - this._useCache = aUseCache; - }, - - // For debugging - dump: function(aPrefix) { - let tabs = BrowserApp.tabs; - for (let i = 0; i < tabs.length; i++) { - dump(aPrefix + " | " + "Tab [" + tabs[i].browser.contentWindow.location.href + "]: lastTouchedAt:" + tabs[i].lastTouchedAt + ", zombie:" + tabs[i].browser.__SS_restore); - } - }, -}; - -function ContextMenuItem(args) { - this.id = uuidgen.generateUUID().toString(); - this.args = args; -} - -ContextMenuItem.prototype = { - get order() { - return this.args.order || 0; - }, - - matches: function(elt, x, y) { - return this.args.selector.matches(elt, x, y); - }, - - callback: function(elt) { - this.args.callback(elt); - }, - - addVal: function(name, elt, defaultValue) { - if (!(name in this.args)) - return defaultValue; - - if (typeof this.args[name] == "function") - return this.args[name](elt); - - return this.args[name]; - }, - - getValue: function(elt) { - return { - id: this.id, - label: this.addVal("label", elt), - showAsActions: this.addVal("showAsActions", elt), - icon: this.addVal("icon", elt), - isGroup: this.addVal("isGroup", elt, false), - inGroup: this.addVal("inGroup", elt, false), - disabled: this.addVal("disabled", elt, false), - selected: this.addVal("selected", elt, false), - isParent: this.addVal("isParent", elt, false), - }; - } -} - -function HTMLContextMenuItem(elt, target) { - ContextMenuItem.call(this, { }); - - this.menuElementRef = Cu.getWeakReference(elt); - this.targetElementRef = Cu.getWeakReference(target); -} - -HTMLContextMenuItem.prototype = Object.create(ContextMenuItem.prototype, { - order: { - value: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - }, - - matches: { - value: function(target) { - let t = this.targetElementRef.get(); - return t === target; - }, - }, - - callback: { - value: function(target) { - let elt = this.menuElementRef.get(); - if (!elt) { - return; - } - - // If this is a menu item, show a new context menu with the submenu in it - if (elt instanceof Ci.nsIDOMHTMLMenuElement) { - try { - NativeWindow.contextmenus.menus = {}; - - let elt = this.menuElementRef.get(); - let target = this.targetElementRef.get(); - if (!elt) { - return; - } - - var items = NativeWindow.contextmenus._getHTMLContextMenuItemsForMenu(elt, target); - // This menu will always only have one context, but we still make sure its the "right" one. - var context = NativeWindow.contextmenus._getContextType(target); - if (items.length > 0) { - NativeWindow.contextmenus._addMenuItems(items, context); - } - - } catch(ex) { - Cu.reportError(ex); - } - } else { - // otherwise just click the menu item - elt.click(); - } - }, - }, - - getValue: { - value: function(target) { - let elt = this.menuElementRef.get(); - if (!elt) { - return null; - } - - if (elt.hasAttribute("hidden")) { - return null; - } - - return { - id: this.id, - icon: elt.icon, - label: elt.label, - disabled: elt.disabled, - menu: elt instanceof Ci.nsIDOMHTMLMenuElement - }; - } - }, -}); - diff --git a/mobile/android/chrome/content/browser.xul b/mobile/android/chrome/content/browser.xul deleted file mode 100644 index 8072a7a1c..000000000 --- a/mobile/android/chrome/content/browser.xul +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> - -<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?> - -<window id="main-window" - onload="BrowserApp.startup();" - windowtype="navigator:browser" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/javascript" src="chrome://browser/content/browser.js"/> - - <deck id="browsers" flex="1"/> - -</window> diff --git a/mobile/android/chrome/content/config.js b/mobile/android/chrome/content/config.js deleted file mode 100644 index 2c868f175..000000000 --- a/mobile/android/chrome/content/config.js +++ /dev/null @@ -1,673 +0,0 @@ -/* 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"; - -var {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components; -Cu.import("resource://gre/modules/Services.jsm"); - -const VKB_ENTER_KEY = 13; // User press of VKB enter key -const INITIAL_PAGE_DELAY = 500; // Initial pause on program start for scroll alignment -const PREFS_BUFFER_MAX = 30; // Max prefs buffer size for getPrefsBuffer() -const PAGE_SCROLL_TRIGGER = 200; // Triggers additional getPrefsBuffer() on user scroll-to-bottom -const FILTER_CHANGE_TRIGGER = 200; // Delay between responses to filterInput changes -const INNERHTML_VALUE_DELAY = 100; // Delay before providing prefs innerHTML value - -var gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties"); -var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); - - -/* ============================== NewPrefDialog ============================== - * - * New Preference Dialog Object and methods - * - * Implements User Interfaces for creation of a single(new) Preference setting - * - */ -var NewPrefDialog = { - - _prefsShield: null, - - _newPrefsDialog: null, - _newPrefItem: null, - _prefNameInputElt: null, - _prefTypeSelectElt: null, - - _booleanValue: null, - _booleanToggle: null, - _stringValue: null, - _intValue: null, - - _positiveButton: null, - - get type() { - return this._prefTypeSelectElt.value; - }, - - set type(aType) { - this._prefTypeSelectElt.value = aType; - switch(this._prefTypeSelectElt.value) { - case "boolean": - this._prefTypeSelectElt.selectedIndex = 0; - break; - case "string": - this._prefTypeSelectElt.selectedIndex = 1; - break; - case "int": - this._prefTypeSelectElt.selectedIndex = 2; - break; - } - - this._newPrefItem.setAttribute("typestyle", aType); - }, - - // Init the NewPrefDialog - init: function AC_init() { - this._prefsShield = document.getElementById("prefs-shield"); - - this._newPrefsDialog = document.getElementById("new-pref-container"); - this._newPrefItem = document.getElementById("new-pref-item"); - this._prefNameInputElt = document.getElementById("new-pref-name"); - this._prefTypeSelectElt = document.getElementById("new-pref-type"); - - this._booleanValue = document.getElementById("new-pref-value-boolean"); - this._stringValue = document.getElementById("new-pref-value-string"); - this._intValue = document.getElementById("new-pref-value-int"); - - this._positiveButton = document.getElementById("positive-button"); - }, - - // Called to update positive button to display text ("Create"/"Change), and enabled/disabled status - // As new pref name is initially displayed, re-focused, or modifed during user input - _updatePositiveButton: function AC_updatePositiveButton(aPrefName) { - this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton"); - this._positiveButton.setAttribute("disabled", true); - if (aPrefName == "") { - return; - } - - // If item already in list, it's being changed, else added - let item = AboutConfig._list.filter(i => { return i.name == aPrefName }); - if (item.length) { - this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton"); - } else { - this._positiveButton.removeAttribute("disabled"); - } - }, - - // When we want to cancel/hide an existing, or show a new pref dialog - toggleShowHide: function AC_toggleShowHide() { - if (this._newPrefsDialog.classList.contains("show")) { - this.hide(); - } else { - this._show(); - } - }, - - // When we want to show the new pref dialog / shield the prefs list - _show: function AC_show() { - this._newPrefsDialog.classList.add("show"); - this._prefsShield.setAttribute("shown", true); - - // Initial default field values - this._prefNameInputElt.value = ""; - this._updatePositiveButton(this._prefNameInputElt.value); - - this.type = "boolean"; - this._booleanValue.value = "false"; - this._stringValue.value = ""; - this._intValue.value = ""; - - this._prefNameInputElt.focus(); - - window.addEventListener("keypress", this.handleKeypress, false); - }, - - // When we want to cancel/hide the new pref dialog / un-shield the prefs list - hide: function AC_hide() { - this._newPrefsDialog.classList.remove("show"); - this._prefsShield.removeAttribute("shown"); - - window.removeEventListener("keypress", this.handleKeypress, false); - }, - - // Watch user key input so we can provide Enter key action, commit input values - handleKeypress: function AC_handleKeypress(aEvent) { - // Close our VKB on new pref enter key press - if (aEvent.keyCode == VKB_ENTER_KEY) - aEvent.target.blur(); - }, - - // New prefs create dialog only allows creating a non-existing preference, doesn't allow for - // Changing an existing one on-the-fly, tap existing/displayed line item pref for that - create: function AC_create(aEvent) { - if (this._positiveButton.getAttribute("disabled") == "true") { - return; - } - - switch(this.type) { - case "boolean": - Services.prefs.setBoolPref(this._prefNameInputElt.value, (this._booleanValue.value == "true") ? true : false); - break; - case "string": - Services.prefs.setCharPref(this._prefNameInputElt.value, this._stringValue.value); - break; - case "int": - Services.prefs.setIntPref(this._prefNameInputElt.value, this._intValue.value); - break; - } - - // Ensure pref adds flushed to disk immediately - Services.prefs.savePrefFile(null); - - this.hide(); - }, - - // Display proper positive button text/state on new prefs name input focus - focusName: function AC_focusName(aEvent) { - this._updatePositiveButton(aEvent.target.value); - }, - - // Display proper positive button text/state as user changes new prefs name - updateName: function AC_updateName(aEvent) { - this._updatePositiveButton(aEvent.target.value); - }, - - // In new prefs dialog, bool prefs are <input type="text">, as they aren't yet tied to an - // Actual Services.prefs.*etBoolPref() - toggleBoolValue: function AC_toggleBoolValue() { - this._booleanValue.value = (this._booleanValue.value == "true" ? "false" : "true"); - } -} - - -/* ============================== AboutConfig ============================== - * - * Main AboutConfig object and methods - * - * Implements User Interfaces for maintenance of a list of Preference settings - * - */ -var AboutConfig = { - - contextMenuLINode: null, - filterInput: null, - _filterPrevInput: null, - _filterChangeTimer: null, - _prefsContainer: null, - _loadingContainer: null, - _list: null, - - // Init the main AboutConfig dialog - init: function AC_init() { - this.filterInput = document.getElementById("filter-input"); - this._prefsContainer = document.getElementById("prefs-container"); - this._loadingContainer = document.getElementById("loading-container"); - - let list = Services.prefs.getChildList(""); - this._list = list.sort().map( function AC_getMapPref(aPref) { - return new Pref(aPref); - }, this); - - // Support filtering about:config via a ?filter=<string> param - let match = /[?&]filter=([^&]+)/i.exec(window.location.href); - if (match) { - this.filterInput.value = decodeURIComponent(match[1]); - } - - // Display the current prefs list (retains searchFilter value) - this.bufferFilterInput(); - - // Setup the prefs observers - Services.prefs.addObserver("", this, false); - }, - - // Uninit the main AboutConfig dialog - uninit: function AC_uninit() { - // Remove the prefs observer - Services.prefs.removeObserver("", this); - }, - - // Clear the filterInput value, to display the entire list - clearFilterInput: function AC_clearFilterInput() { - this.filterInput.value = ""; - this.bufferFilterInput(); - }, - - // Buffer down rapid changes in filterInput value from keyboard - bufferFilterInput: function AC_bufferFilterInput() { - if (this._filterChangeTimer) { - clearTimeout(this._filterChangeTimer); - } - - this._filterChangeTimer = setTimeout((function() { - this._filterChangeTimer = null; - // Display updated prefs list when filterInput value settles - this._displayNewList(); - }).bind(this), FILTER_CHANGE_TRIGGER); - }, - - // Update displayed list when filterInput value changes - _displayNewList: function AC_displayNewList() { - // This survives the search filter value past a page refresh - this.filterInput.setAttribute("value", this.filterInput.value); - - // Don't start new filter search if same as last - if (this.filterInput.value == this._filterPrevInput) { - return; - } - this._filterPrevInput = this.filterInput.value; - - // Clear list item selection / context menu, prefs list, get first buffer, set scrolling on - this.selected = ""; - this._clearPrefsContainer(); - this._addMorePrefsToContainer(); - window.onscroll = this.onScroll.bind(this); - - // Pause for screen to settle, then ensure at top - setTimeout((function() { - window.scrollTo(0, 0); - }).bind(this), INITIAL_PAGE_DELAY); - }, - - // Clear the displayed preferences list - _clearPrefsContainer: function AC_clearPrefsContainer() { - // Quick clear the prefsContainer list - let empty = this._prefsContainer.cloneNode(false); - this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer); - this._prefsContainer = empty; - - // Quick clear the prefs li.HTML list - this._list.forEach(function(item) { - delete item.li; - }); - }, - - // Get a small manageable block of prefs items, and add them to the displayed list - _addMorePrefsToContainer: function AC_addMorePrefsToContainer() { - // Create filter regex - let filterExp = this.filterInput.value ? - new RegExp(this.filterInput.value, "i") : null; - - // Get a new block for the display list - let prefsBuffer = []; - for (let i = 0; i < this._list.length && prefsBuffer.length < PREFS_BUFFER_MAX; i++) { - if (!this._list[i].li && this._list[i].test(filterExp)) { - prefsBuffer.push(this._list[i]); - } - } - - // Add the new block to the displayed list - for (let i = 0; i < prefsBuffer.length; i++) { - this._prefsContainer.appendChild(prefsBuffer[i].getOrCreateNewLINode()); - } - - // Determine if anything left to add later by scrolling - let anotherPrefsBufferRemains = false; - for (let i = 0; i < this._list.length; i++) { - if (!this._list[i].li && this._list[i].test(filterExp)) { - anotherPrefsBufferRemains = true; - break; - } - } - - if (anotherPrefsBufferRemains) { - // If still more could be displayed, show the throbber - this._loadingContainer.style.display = "block"; - } else { - // If no more could be displayed, hide the throbber, and stop noticing scroll events - this._loadingContainer.style.display = "none"; - window.onscroll = null; - } - }, - - // If scrolling at the bottom, maybe add some more entries - onScroll: function AC_onScroll(aEvent) { - if (this._prefsContainer.scrollHeight - (window.pageYOffset + window.innerHeight) < PAGE_SCROLL_TRIGGER) { - if (!this._filterChangeTimer) { - this._addMorePrefsToContainer(); - } - } - }, - - - // Return currently selected list item node - get selected() { - return document.querySelector(".pref-item.selected"); - }, - - // Set list item node as selected - set selected(aSelection) { - let currentSelection = this.selected; - if (aSelection == currentSelection) { - return; - } - - // Clear any previous selection - if (currentSelection) { - currentSelection.classList.remove("selected"); - currentSelection.removeEventListener("keypress", this.handleKeypress, false); - } - - // Set any current selection - if (aSelection) { - aSelection.classList.add("selected"); - aSelection.addEventListener("keypress", this.handleKeypress, false); - } - }, - - // Watch user key input so we can provide Enter key action, commit input values - handleKeypress: function AC_handleKeypress(aEvent) { - if (aEvent.keyCode == VKB_ENTER_KEY) - aEvent.target.blur(); - }, - - // Return the target list item node of an action event - getLINodeForEvent: function AC_getLINodeForEvent(aEvent) { - let node = aEvent.target; - while (node && node.nodeName != "li") { - node = node.parentNode; - } - - return node; - }, - - // Return a pref of a list item node - _getPrefForNode: function AC_getPrefForNode(aNode) { - let pref = aNode.getAttribute("name"); - - return new Pref(pref); - }, - - // When list item name or value are tapped - selectOrToggleBoolPref: function AC_selectOrToggleBoolPref(aEvent) { - let node = this.getLINodeForEvent(aEvent); - - // If not already selected, just do so - if (this.selected != node) { - this.selected = node; - return; - } - - // If already selected, and value is boolean, toggle it - let pref = this._getPrefForNode(node); - if (pref.type != Services.prefs.PREF_BOOL) { - return; - } - - this.toggleBoolPref(aEvent); - }, - - // When finalizing list input values due to blur - setIntOrStringPref: function AC_setIntOrStringPref(aEvent) { - let node = this.getLINodeForEvent(aEvent); - - // Skip if locked - let pref = this._getPrefForNode(node); - if (pref.locked) { - return; - } - - // Boolean inputs blur to remove focus from "button" - if (pref.type == Services.prefs.PREF_BOOL) { - return; - } - - // String and Int inputs change / commit on blur - pref.value = aEvent.target.value; - }, - - // When we reset a pref to it's default value (note resetting a user created pref will delete it) - resetDefaultPref: function AC_resetDefaultPref(aEvent) { - let node = this.getLINodeForEvent(aEvent); - - // If not already selected, do so - if (this.selected != node) { - this.selected = node; - } - - // Reset will handle any locked condition - let pref = this._getPrefForNode(node); - pref.reset(); - - // Ensure pref reset flushed to disk immediately - Services.prefs.savePrefFile(null); - }, - - // When we want to toggle a bool pref - toggleBoolPref: function AC_toggleBoolPref(aEvent) { - let node = this.getLINodeForEvent(aEvent); - - // Skip if locked, or not boolean - let pref = this._getPrefForNode(node); - if (pref.locked) { - return; - } - - // Toggle, and blur to remove field focus - pref.value = !pref.value; - aEvent.target.blur(); - }, - - // When Int inputs have their Up or Down arrows toggled - incrOrDecrIntPref: function AC_incrOrDecrIntPref(aEvent, aInt) { - let node = this.getLINodeForEvent(aEvent); - - // Skip if locked - let pref = this._getPrefForNode(node); - if (pref.locked) { - return; - } - - pref.value += aInt; - }, - - // Observe preference changes - observe: function AC_observe(aSubject, aTopic, aPrefName) { - let pref = new Pref(aPrefName); - - // Ignore uninteresting changes, and avoid "private" preferences - if (aTopic != "nsPref:changed") { - return; - } - - // If pref type invalid, refresh display as user reset/removed an item from the list - if (pref.type == Services.prefs.PREF_INVALID) { - document.location.reload(); - return; - } - - // If pref onscreen, update in place. - let item = document.querySelector(".pref-item[name=\"" + CSS.escape(pref.name) + "\"]"); - if (item) { - item.setAttribute("value", pref.value); - let input = item.querySelector("input"); - input.setAttribute("value", pref.value); - input.value = pref.value; - - pref.default ? - item.querySelector(".reset").setAttribute("disabled", "true") : - item.querySelector(".reset").removeAttribute("disabled"); - return; - } - - // If pref not already in list, refresh display as it's being added - let anyWhere = this._list.filter(i => { return i.name == pref.name }); - if (!anyWhere.length) { - document.location.reload(); - } - }, - - // Quick context menu helpers for about:config - clipboardCopy: function AC_clipboardCopy(aField) { - let pref = this._getPrefForNode(this.contextMenuLINode); - if (aField == 'name') { - gClipboardHelper.copyString(pref.name); - } else { - gClipboardHelper.copyString(pref.value); - } - } -} - - -/* ============================== Pref ============================== - * - * Individual Preference object / methods - * - * Defines a Pref object, a document list item tied to Preferences Services - * And the methods by which they interact. - * - */ -function Pref(aName) { - this.name = aName; -} - -Pref.prototype = { - get type() { - return Services.prefs.getPrefType(this.name); - }, - - get value() { - switch (this.type) { - case Services.prefs.PREF_BOOL: - return Services.prefs.getBoolPref(this.name); - case Services.prefs.PREF_INT: - return Services.prefs.getIntPref(this.name); - case Services.prefs.PREF_STRING: - default: - return Services.prefs.getCharPref(this.name); - } - - }, - set value(aPrefValue) { - switch (this.type) { - case Services.prefs.PREF_BOOL: - Services.prefs.setBoolPref(this.name, aPrefValue); - break; - case Services.prefs.PREF_INT: - Services.prefs.setIntPref(this.name, aPrefValue); - break; - case Services.prefs.PREF_STRING: - default: - Services.prefs.setCharPref(this.name, aPrefValue); - } - - // Ensure pref change flushed to disk immediately - Services.prefs.savePrefFile(null); - }, - - get default() { - return !Services.prefs.prefHasUserValue(this.name); - }, - - get locked() { - return Services.prefs.prefIsLocked(this.name); - }, - - reset: function AC_reset() { - Services.prefs.clearUserPref(this.name); - }, - - test: function AC_test(aValue) { - return aValue ? aValue.test(this.name) : true; - }, - - // Get existing or create new LI node for the pref - getOrCreateNewLINode: function AC_getOrCreateNewLINode() { - if (!this.li) { - this.li = document.createElement("li"); - - this.li.className = "pref-item"; - this.li.setAttribute("name", this.name); - - // Click callback to ensure list item selected even on no-action tap events - this.li.addEventListener("click", - function(aEvent) { - AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent); - }, - false - ); - - // Contextmenu callback to identify selected list item - this.li.addEventListener("contextmenu", - function(aEvent) { - AboutConfig.contextMenuLINode = AboutConfig.getLINodeForEvent(aEvent); - }, - false - ); - - this.li.setAttribute("contextmenu", "prefs-context-menu"); - - // Create list item outline, bind to object actions - this.li.innerHTML = - "<div class='pref-name' " + - "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" + - this.name + - "</div>" + - "<div class='pref-item-line'>" + - "<input class='pref-value' value='' " + - "onblur='AboutConfig.setIntOrStringPref(event);' " + - "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" + - "</input>" + - "<div class='pref-button reset' " + - "onclick='AboutConfig.resetDefaultPref(event);'>" + - gStringBundle.GetStringFromName("pref.resetButton") + - "</div>" + - "<div class='pref-button toggle' " + - "onclick='AboutConfig.toggleBoolPref(event);'>" + - gStringBundle.GetStringFromName("pref.toggleButton") + - "</div>" + - "<div class='pref-button up' " + - "onclick='AboutConfig.incrOrDecrIntPref(event, 1);'>" + - "</div>" + - "<div class='pref-button down' " + - "onclick='AboutConfig.incrOrDecrIntPref(event, -1);'>" + - "</div>" + - "</div>"; - - // Delay providing the list item values, until the LI is returned and added to the document - setTimeout(this._valueSetup.bind(this), INNERHTML_VALUE_DELAY); - } - - return this.li; - }, - - // Initialize list item object values - _valueSetup: function AC_valueSetup() { - - this.li.setAttribute("type", this.type); - this.li.setAttribute("value", this.value); - - let valDiv = this.li.querySelector(".pref-value"); - valDiv.value = this.value; - - switch(this.type) { - case Services.prefs.PREF_BOOL: - valDiv.setAttribute("type", "button"); - this.li.querySelector(".up").setAttribute("disabled", true); - this.li.querySelector(".down").setAttribute("disabled", true); - break; - case Services.prefs.PREF_STRING: - valDiv.setAttribute("type", "text"); - this.li.querySelector(".up").setAttribute("disabled", true); - this.li.querySelector(".down").setAttribute("disabled", true); - this.li.querySelector(".toggle").setAttribute("disabled", true); - break; - case Services.prefs.PREF_INT: - valDiv.setAttribute("type", "number"); - this.li.querySelector(".toggle").setAttribute("disabled", true); - break; - } - - this.li.setAttribute("default", this.default); - if (this.default) { - this.li.querySelector(".reset").setAttribute("disabled", true); - } - - if (this.locked) { - valDiv.setAttribute("disabled", this.locked); - this.li.querySelector(".pref-name").setAttribute("locked", true); - } - } -} - diff --git a/mobile/android/chrome/content/config.xhtml b/mobile/android/chrome/content/config.xhtml deleted file mode 100644 index fd40bb517..000000000 --- a/mobile/android/chrome/content/config.xhtml +++ /dev/null @@ -1,86 +0,0 @@ -<?xml version="1.0"?> - -<!-- 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/. --> - -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" > -%globalDTD; -<!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd"> -%configDTD; -]> - -<html xmlns="http://www.w3.org/1999/xhtml"> - -<head> - <meta name="viewport" content="width=device-width; user-scalable=0" /> - - <link rel="stylesheet" href="chrome://browser/skin/config.css" type="text/css"/> - <script type="text/javascript;version=1.8" src="chrome://browser/content/config.js"></script> -</head> - -<body dir="&locale.dir;" onload="NewPrefDialog.init(); AboutConfig.init();" - onunload="AboutConfig.uninit();"> - - <div class="toolbar"> - <div class="toolbar-container"> - <div id="new-pref-toggle-button" onclick="NewPrefDialog.toggleShowHide();"/> - - <div class="toolbar-item" id="filter-container"> - <div id="filter-search-button"/> - <input id="filter-input" type="search" placeholder="&toolbar.searchPlaceholder;" value="" - oninput="AboutConfig.bufferFilterInput();"/> - <div id="filter-input-clear-button" onclick="AboutConfig.clearFilterInput();"/> - </div> - </div> - </div> - - <div id="content" ontouchstart="AboutConfig.filterInput.blur();"> - - <div id="new-pref-container"> - <li class="pref-item" id="new-pref-item"> - <div class="pref-item-line"> - <input class="pref-name" id="new-pref-name" type="text" placeholder="&newPref.namePlaceholder;" - onfocus="NewPrefDialog.focusName(event);" - oninput="NewPrefDialog.updateName(event);"/> - <select class="pref-value" id="new-pref-type" onchange="NewPrefDialog.type = event.target.value;"> - <option value="boolean">&newPref.valueBoolean;</option> - <option value="string">&newPref.valueString;</option> - <option value="int">&newPref.valueInteger;</option> - </select> - </div> - - <div class="pref-item-line" id="new-pref-line-boolean"> - <input class="pref-value" id="new-pref-value-boolean" disabled="disabled"/> - <div class="pref-button toggle" onclick="NewPrefDialog.toggleBoolValue();">&newPref.toggleButton;</div> - </div> - - <div class="pref-item-line"> - <input class="pref-value" id="new-pref-value-string" placeholder="&newPref.stringPlaceholder;"/> - <input class="pref-value" id="new-pref-value-int" placeholder="&newPref.numberPlaceholder;" type="number"/> - </div> - - <div class="pref-item-line"> - <div class="pref-button cancel" id="negative-button" onclick="NewPrefDialog.hide();">&newPref.cancelButton;</div> - <div class="pref-button create" id="positive-button" onclick="NewPrefDialog.create(event);"></div> - </div> - </li> - </div> - - <div id="prefs-shield"></div> - - <ul id="prefs-container"/> - - <ul id="loading-container"><li></li></ul> - - </div> - - <menu type="context" id="prefs-context-menu"> - <menuitem label="&contextMenu.copyPrefName;" onclick="AboutConfig.clipboardCopy('name');"></menuitem> - <menuitem label="&contextMenu.copyPrefValue;" onclick="AboutConfig.clipboardCopy('value');"></menuitem> - </menu> - -</body> -</html> diff --git a/mobile/android/chrome/content/content.js b/mobile/android/chrome/content/content.js deleted file mode 100644 index 7cac22bd1..000000000 --- a/mobile/android/chrome/content/content.js +++ /dev/null @@ -1,159 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -var { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -Cu.import("resource://gre/modules/ExtensionContent.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "AboutReader", "resource://gre/modules/AboutReader.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm"); - -var dump = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "Content"); - -var global = this; - -// This is copied from desktop's tab-content.js. See bug 1153485 about sharing this code somehow. -var AboutReaderListener = { - - _articlePromise: null, - - _isLeavingReaderMode: false, - - init: function() { - addEventListener("AboutReaderContentLoaded", this, false, true); - addEventListener("DOMContentLoaded", this, false); - addEventListener("pageshow", this, false); - addEventListener("pagehide", this, false); - addMessageListener("Reader:ToggleReaderMode", this); - addMessageListener("Reader:PushState", this); - }, - - receiveMessage: function(message) { - switch (message.name) { - case "Reader:ToggleReaderMode": - let url = content.document.location.href; - if (!this.isAboutReader) { - this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError); - ReaderMode.enterReaderMode(docShell, content); - } else { - this._isLeavingReaderMode = true; - ReaderMode.leaveReaderMode(docShell, content); - } - break; - - case "Reader:PushState": - this.updateReaderButton(!!(message.data && message.data.isArticle)); - break; - } - }, - - get isAboutReader() { - return content.document.documentURI.startsWith("about:reader"); - }, - - handleEvent: function(aEvent) { - if (aEvent.originalTarget.defaultView != content) { - return; - } - - switch (aEvent.type) { - case "AboutReaderContentLoaded": - if (!this.isAboutReader) { - return; - } - - // If we are restoring multiple reader mode tabs during session restore, duplicate "DOMContentLoaded" - // events may be fired for the visible tab. The inital "DOMContentLoaded" may be received before the - // document body is available, so we avoid instantiating an AboutReader object, expecting that a - // valid message will follow. See bug 925983. - if (content.document.body) { - new AboutReader(global, content, this._articlePromise); - this._articlePromise = null; - } - break; - - case "pagehide": - // this._isLeavingReaderMode is used here to keep the Reader Mode icon - // visible in the location bar when transitioning from reader-mode page - // back to the source page. - sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderMode }); - if (this._isLeavingReaderMode) { - this._isLeavingReaderMode = false; - } - break; - - case "pageshow": - // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded" - // event, so we need to rely on "pageshow" in this case. - if (aEvent.persisted) { - this.updateReaderButton(); - } - break; - case "DOMContentLoaded": - this.updateReaderButton(); - break; - } - }, - updateReaderButton: function(forceNonArticle) { - if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader || - !(content.document instanceof content.HTMLDocument) || - content.document.mozSyntheticDocument) { - return; - } - - this.scheduleReadabilityCheckPostPaint(forceNonArticle); - }, - - cancelPotentialPendingReadabilityCheck: function() { - if (this._pendingReadabilityCheck) { - removeEventListener("MozAfterPaint", this._pendingReadabilityCheck); - delete this._pendingReadabilityCheck; - } - }, - - scheduleReadabilityCheckPostPaint: function(forceNonArticle) { - if (this._pendingReadabilityCheck) { - // We need to stop this check before we re-add one because we don't know - // if forceNonArticle was true or false last time. - this.cancelPotentialPendingReadabilityCheck(); - } - this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(this, forceNonArticle); - addEventListener("MozAfterPaint", this._pendingReadabilityCheck); - }, - - onPaintWhenWaitedFor: function(forceNonArticle, event) { - // In non-e10s, we'll get called for paints other than ours, and so it's - // possible that this page hasn't been laid out yet, in which case we - // should wait until we get an event that does relate to our layout. We - // determine whether any of our content got painted by checking if there - // are any painted rects. - if (!event.clientRects.length) { - return; - } - - this.cancelPotentialPendingReadabilityCheck(); - - // Only send updates when there are articles; there's no point updating with - // |false| all the time. - if (ReaderMode.isProbablyReaderable(content.document)) { - sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true }); - } else if (forceNonArticle) { - sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false }); - } - }, -}; -AboutReaderListener.init(); - -addMessageListener("RemoteLogins:fillForm", function(message) { - LoginManagerContent.receiveMessage(message, content); -}); - -ExtensionContent.init(this); -addEventListener("unload", () => { - ExtensionContent.uninit(this); -}); diff --git a/mobile/android/chrome/content/geckoview.js b/mobile/android/chrome/content/geckoview.js deleted file mode 100644 index b4685a8d3..000000000 --- a/mobile/android/chrome/content/geckoview.js +++ /dev/null @@ -1,32 +0,0 @@ -/* 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"; - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; -var Cr = Components.results; - -Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Log", - "resource://gre/modules/AndroidLog.jsm", "AndroidLog"); - -XPCOMUtils.defineLazyModuleGetter(this, "Messaging", - "resource://gre/modules/Messaging.jsm", "Messaging"); - -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm", "Services"); - -function dump(msg) { - Log.d("View", msg); -} - -function startup() { - dump("zerdatime " + Date.now() + " - geckoivew chrome startup finished."); - - // Notify Java that Gecko has loaded. - Messaging.sendRequest({ type: "Gecko:Ready" }); -} diff --git a/mobile/android/chrome/content/geckoview.xul b/mobile/android/chrome/content/geckoview.xul deleted file mode 100644 index a3d4d1290..000000000 --- a/mobile/android/chrome/content/geckoview.xul +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> - -<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?> - -<window id="main-window" - onload="startup();" - windowtype="navigator:browser" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <browser id="content" type="content-primary" src="https://mozilla.com" flex="1" remote="true"/> - - <script type="application/javascript" src="chrome://browser/content/geckoview.js"/> -</window> diff --git a/mobile/android/chrome/content/healthreport-prefs.js b/mobile/android/chrome/content/healthreport-prefs.js deleted file mode 100644 index 5c4a50d38..000000000 --- a/mobile/android/chrome/content/healthreport-prefs.js +++ /dev/null @@ -1,6 +0,0 @@ -#filter substitution -/* 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/. */ - -pref("datareporting.healthreport.about.reportUrl", "https://fhr.cdn.mozilla.net/%LOCALE%/mobile/"); diff --git a/mobile/android/chrome/content/languages.properties b/mobile/android/chrome/content/languages.properties deleted file mode 100644 index 53d30d125..000000000 --- a/mobile/android/chrome/content/languages.properties +++ /dev/null @@ -1,114 +0,0 @@ -# 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/. - -# LOCALIZATION NOTE: do not localize -af=Afrikaans -ak=Akan -ar=عربي -as=অসমীয়া -ast-ES=Asturianu -be=Беларуская -bg=Български -bn-BD=বাংলা (বাংলাদেশ) -bn-IN=বাংলা (ভারত) -br-FR=Brezhoneg -ca=català -ca-valencia=català (valencià) -cs=Čeština -cy=Cymraeg -da=Dansk -de=Deutsch -de-AT=Deutsch (Österreich) -de-CH=Deutsch (Schweiz) -de-DE=Deutsch (Deutschland) -el=Ελληνικά -en-AU=English (Australian) -en-CA=English (Canadian) -en-GB=English (British) -en-NZ=English (New Zealand) -en-US=English (US) -en-ZA=English (South African) -eo=Esperanto -es-AR=Español (de Argentina) -es-CL=Español (de Chile) -es-ES=Español (de España) -es-MX=Español (de México) -et=Eesti keel -eu=Euskara -fa=فارسی -fi=suomi -fr=Français -fur-IT=Furlan -fy-NL=Frysk -ga-IE=Gaeilge -gl=Galego -gu-IN=ગુજરાતી -he=עברית -hi=हिन्दी -hi-IN=हिन्दी (भारत) -hr=Hrvatski -hsb=Hornjoserbsce -hu=Magyar -hy-AM=Հայերեն -id=Bahasa Indonesia -is=íslenska -it=Italiano -ja=日本語 -ka=ქართული -kk=Қазақ -kn=ಕನ್ನಡ -ko=한국어 -ku=Kurdî -la=Latina -lt=lietuvių -lv=Latviešu -mg=Malagasy -mi=Māori (Aotearoa) -mk=Македонски -ml=മലയാളം -mn=Монгол -mr=मराठी -nb-NO=Norsk bokmål -ne-NP=नेपाली -nl=Nederlands -nn-NO=Norsk nynorsk -nr=isiNdebele Sepumalanga -nso=Sepedi -oc=occitan (lengadocian) -or=ଓଡ଼ିଆ -pa-IN=ਪੰਜਾਬੀ -pl=Polski -pt-BR=Português (do Brasil) -pt-PT=Português (Europeu) -rm=rumantsch -ro=română -ru=Русский -rw=Ikinyarwanda -si=සිංහල -sk=slovenčina -sl=slovensko -sq=Shqip -sr=Српски -sr-Latn=Srpski -ss=Siswati -st=Sesotho -sv-SE=Svenska -ta=தமிழ் -ta-IN=தமிழ் (இந்தியா) -ta-LK=தமிழ் (இலங்கை) -te=తెలుగు -th=ไทย -tn=Setswana -tr=Türkçe -ts=Mutsonga -tt-RU=Tatarça -uk=Українська -ur=اُردو -ve=Tshivenḓa -vi=Tiếng Việt -wo=Wolof -xh=isiXhosa -zh-CN=中文 (简体) -zh-TW=正體中文 (繁體) -zu=isiZulu diff --git a/mobile/android/chrome/content/netError.xhtml b/mobile/android/chrome/content/netError.xhtml deleted file mode 100644 index f4c727c06..000000000 --- a/mobile/android/chrome/content/netError.xhtml +++ /dev/null @@ -1,406 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!DOCTYPE html [ - <!ENTITY % htmlDTD - PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "DTD/xhtml1-strict.dtd"> - %htmlDTD; - <!ENTITY % netErrorDTD - SYSTEM "chrome://global/locale/netError.dtd"> - %netErrorDTD; - <!ENTITY % globalDTD - SYSTEM "chrome://global/locale/global.dtd"> - %globalDTD; -]> - -<!-- 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/. --> - -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <meta name="viewport" content="width=device-width; user-scalable=false;" /> - <title>&loadError.label;</title> - <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" /> - <!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in - toolkit/components/places/src/nsFaviconService.h should be updated. --> - <link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/errorpage-warning.png"/> - - <script type="application/javascript"><![CDATA[ - // Error url MUST be formatted like this: - // moz-neterror:page?e=error&u=url&d=desc - // - // or optionally, to specify an alternate CSS class to allow for - // custom styling and favicon: - // - // moz-neterror:page?e=error&u=url&s=classname&d=desc - - // Note that this file uses document.documentURI to get - // the URL (with the format from above). This is because - // document.location.href gets the current URI off the docshell, - // which is the URL displayed in the location bar, i.e. - // the URI that the user attempted to load. - - function getErrorCode() - { - var url = document.documentURI; - var error = url.search(/e\=/); - var duffUrl = url.search(/\&u\=/); - return decodeURIComponent(url.slice(error + 2, duffUrl)); - } - - function getCSSClass() - { - var url = document.documentURI; - var matches = url.match(/s\=([^&]+)\&/); - // s is optional, if no match just return nothing - if (!matches || matches.length < 2) - return ""; - - // parenthetical match is the second entry - return decodeURIComponent(matches[1]); - } - - function getDescription() - { - var url = document.documentURI; - var desc = url.search(/d\=/); - - // desc == -1 if not found; if so, return an empty string - // instead of what would turn out to be portions of the URI - if (desc == -1) - return ""; - - return decodeURIComponent(url.slice(desc + 2)); - } - - function retryThis(buttonEl) - { - // Note: The application may wish to handle switching off "offline mode" - // before this event handler runs, but using a capturing event handler. - - // Session history has the URL of the page that failed - // to load, not the one of the error page. So, just call - // reload(), which will also repost POST data correctly. - try { - location.reload(); - } catch (e) { - // We probably tried to reload a URI that caused an exception to - // occur; e.g. a nonexistent file. - } - } - - function initPage() - { - var err = getErrorCode(); - - // if it's an unknown error or there's no title or description - // defined, get the generic message - var errTitle = document.getElementById("et_" + err); - var errDesc = document.getElementById("ed_" + err); - if (!errTitle || !errDesc) - { - errTitle = document.getElementById("et_generic"); - errDesc = document.getElementById("ed_generic"); - } - - var title = document.getElementsByClassName("errorTitleText")[0]; - if (title) - { - title.parentNode.replaceChild(errTitle, title); - // change id to the replaced child's id so styling works - errTitle.classList.add("errorTitleText"); - } - - var sd = document.getElementById("errorShortDescText"); - if (sd) - sd.textContent = getDescription(); - - var ld = document.getElementById("errorLongDesc"); - if (ld) - { - ld.parentNode.replaceChild(errDesc, ld); - // change id to the replaced child's id so styling works - errDesc.id = "errorLongDesc"; - } - - // remove undisplayed errors to avoid bug 39098 - var errContainer = document.getElementById("errorContainer"); - errContainer.parentNode.removeChild(errContainer); - - var className = getCSSClass(); - if (className && className != "expertBadCert") { - // Associate a CSS class with the root of the page, if one was passed in, - // to allow custom styling. - // Not "expertBadCert" though, don't want to deal with the favicon - document.documentElement.className = className; - - // Also, if they specified a CSS class, they must supply their own - // favicon. In order to trigger the browser to repaint though, we - // need to remove/add the link element. - var favicon = document.getElementById("favicon"); - var faviconParent = favicon.parentNode; - faviconParent.removeChild(favicon); - favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png"); - faviconParent.appendChild(favicon); - } - if (className == "expertBadCert") { - showSecuritySection(); - } - - if (err == "remoteXUL") { - // Remove the "Try again" button for remote XUL errors given that - // it is useless. - document.getElementById("errorTryAgain").style.display = "none"; - } - - if (err == "cspBlocked") { - // Remove the "Try again" button for CSP violations, since it's - // almost certainly useless. (Bug 553180) - document.getElementById("errorTryAgain").style.display = "none"; - } - - if (err == "nssBadCert") { - // Remove the "Try again" button for security exceptions, since it's - // almost certainly useless. - document.getElementById("errorTryAgain").style.display = "none"; - document.getElementById("errorPage").setAttribute("class", "certerror"); - } - else { - // Remove the override block for non-certificate errors. CSS-hiding - // isn't good enough here, because of bug 39098 - var secOverride = document.getElementById("securityOverrideDiv"); - secOverride.parentNode.removeChild(secOverride); - } - - if (err == "inadequateSecurityError") { - // Remove the "Try again" button for HTTP/2 inadequate security as it - // is useless. - document.getElementById("errorTryAgain").style.display = "none"; - - var container = document.getElementById("errorLongDesc"); - for (var span of container.querySelectorAll("span.hostname")) { - span.textContent = document.location.hostname; - } - } - - addDomainErrorLinks(); - } - - function showSecuritySection() { - // Swap link out, content in - document.getElementById('securityOverrideContent').style.display = ''; - document.getElementById('securityOverrideLink').style.display = 'none'; - } - - /* Try to preserve the links contained in the error description, like - the error code. - - Also, in the case of SSL error pages about domain mismatch, see if - we can hyperlink the user to the correct site. We don't want - to do this generically since it allows MitM attacks to redirect - users to a site under attacker control, but in certain cases - it is safe (and helpful!) to do so. Bug 402210 - */ - function addDomainErrorLinks() { - // Rather than textContent, we need to treat description as HTML - var sd = document.getElementById("errorShortDescText"); - if (sd) { - var desc = getDescription(); - - // sanitize description text - see bug 441169 - - // First, find the index of the <a> tags we care about, being - // careful not to use an over-greedy regex. - var codeRe = /<a id="errorCode" title="([^"]+)">/; - var codeResult = codeRe.exec(desc); - var domainRe = /<a id="cert_domain_link" title="([^"]+)">/; - var domainResult = domainRe.exec(desc); - - // The order of these links in the description is fixed in - // TransportSecurityInfo.cpp:formatOverridableCertErrorMessage. - var firstResult = domainResult; - if(!domainResult) - firstResult = codeResult; - if (!firstResult) - return; - // Remove sd's existing children - sd.textContent = ""; - - // Everything up to the first link should be text content. - sd.appendChild(document.createTextNode(desc.slice(0, firstResult.index))); - - // Now create the actual links. - if (domainResult) { - createLink(sd, "cert_domain_link", domainResult[1]) - // Append text for anything between the two links. - sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length, codeResult.index))); - } - createLink(sd, "errorCode", codeResult[1]) - - // Finally, append text for anything after the last closing </a>. - sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length))); - } - - // Initialize the cert domain link. - var link = document.getElementById('cert_domain_link'); - if (!link) - return; - - var okHost = link.getAttribute("title"); - var thisHost = document.location.hostname; - var proto = document.location.protocol; - - // If okHost is a wildcard domain ("*.example.com") let's - // use "www" instead. "*.example.com" isn't going to - // get anyone anywhere useful. bug 432491 - okHost = okHost.replace(/^\*\./, "www."); - - /* case #1: - * example.com uses an invalid security certificate. - * - * The certificate is only valid for www.example.com - * - * Make sure to include the "." ahead of thisHost so that - * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com" - * - * We'd normally just use a RegExp here except that we lack a - * library function to escape them properly (bug 248062), and - * domain names are famous for having '.' characters in them, - * which would allow spurious and possibly hostile matches. - */ - if (okHost.endsWith("." + thisHost)) - link.href = proto + okHost; - - /* case #2: - * browser.garage.maemo.org uses an invalid security certificate. - * - * The certificate is only valid for garage.maemo.org - */ - if (thisHost.endsWith("." + okHost)) - link.href = proto + okHost; - } - - function createLink(el, id, text) { - var anchorEl = document.createElement("a"); - anchorEl.setAttribute("id", id); - anchorEl.setAttribute("title", text); - anchorEl.appendChild(document.createTextNode(text)); - el.appendChild(anchorEl); - } - ]]></script> - </head> - - <body id="errorPage" dir="&locale.dir;"> - - <!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) --> - <div id="errorContainer"> - <div id="errorTitlesContainer"> - <h1 id="et_generic">&generic.title;</h1> - <h1 id="et_dnsNotFound">&dnsNotFound.title;</h1> - <h1 id="et_fileNotFound">&fileNotFound.title;</h1> - <h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1> - <h1 id="et_malformedURI">&malformedURI.title;</h1> - <h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1> - <h1 id="et_connectionFailure">&connectionFailure.title;</h1> - <h1 id="et_netTimeout">&netTimeout.title;</h1> - <h1 id="et_redirectLoop">&redirectLoop.title;</h1> - <h1 id="et_unknownSocketType">&unknownSocketType.title;</h1> - <h1 id="et_netReset">&netReset.title;</h1> - <h1 id="et_notCached">¬Cached.title;</h1> - - <!-- Since Fennec not yet have offline mode, change the title to - connectionFailure to prevent confusion --> - <h1 id="et_netOffline">&connectionFailure.title;</h1> - - <h1 id="et_netInterrupt">&netInterrupt.title;</h1> - <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1> - <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1> - <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1> - <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1> - <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1> - <h1 id="et_nssFailure2">&nssFailure2.title;</h1> - <h1 id="et_nssBadCert">&nssBadCert.title;</h1> - <h1 id="et_cspBlocked">&cspBlocked.title;</h1> - <h1 id="et_remoteXUL">&remoteXUL.title;</h1> - <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1> - <h1 id="et_sslv3Used">&sslv3Used.title;</h1> - <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1> - <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1> - </div> - <div id="errorDescriptionsContainer"> - <div id="ed_generic">&generic.longDesc;</div> - <div id="ed_dnsNotFound">&dnsNotFound.longDesc4;</div> - <div id="ed_fileNotFound">&fileNotFound.longDesc;</div> - <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div> - <div id="ed_malformedURI">&malformedURI.longDesc2;</div> - <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div> - <div id="ed_connectionFailure">&connectionFailure.longDesc2;</div> - <div id="ed_netTimeout">&netTimeout.longDesc2;</div> - <div id="ed_redirectLoop">&redirectLoop.longDesc;</div> - <div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div> - <div id="ed_netReset">&netReset.longDesc2;</div> - <div id="ed_notCached">¬Cached.longDesc;</div> - - <!-- Change longDesc from netOffline to connectionFailure, - suggesting user to check their wifi/cell_data connection --> - <div id="ed_netOffline">&connectionFailure.longDesc2;</div> - - <div id="ed_netInterrupt">&netInterrupt.longDesc2;</div> - <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div> - <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc3;</div> - <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div> - <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div> - <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div> - <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div> - <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div> - <div id="ed_cspBlocked">&cspBlocked.longDesc;</div> - <div id="ed_remoteXUL">&remoteXUL.longDesc;</div> - <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div> - <div id="ed_sslv3Used">&sslv3Used.longDesc;</div> - <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc;</div> - <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div> - </div> - </div> - - <!-- PAGE CONTAINER (for styling purposes only) --> - <div id="errorPageContainer"> - - <!-- Error Title --> - <div id="errorTitle"> - <h1 class="errorTitleText" /> - </div> - - <!-- LONG CONTENT (the section most likely to require scrolling) --> - <div id="errorLongContent"> - - <!-- Short Description --> - <div id="errorShortDesc"> - <p id="errorShortDescText" /> - </div> - - <!-- Long Description (Note: See netError.dtd for used XHTML tags) --> - <div id="errorLongDesc" /> - - <!-- Override section - For ssl errors only. Removed on init for other - error types. --> - <div id="securityOverrideDiv"> - <a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a> - <div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div> - </div> - </div> - - <!-- Retry Button --> - <button id="errorTryAgain" onclick="retryThis(this);">&retry.label;</button> - - </div> - - <!-- - - Note: It is important to run the script this way, instead of using - - an onload handler. This is because error pages are loaded as - - LOAD_BACKGROUND, which means that onload handlers will not be executed. - --> - <script type="application/javascript">initPage();</script> - - </body> -</html> |