| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.preferences.website; |
| |
| import org.chromium.chrome.browser.ContentSettingsType; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Utility class that asynchronously fetches any Websites and the permissions |
| * that the user has set for them. |
| */ |
| public class WebsitePermissionsFetcher { |
| /** |
| * A callback to pass to WebsitePermissionsFetcher. This is run when the |
| * website permissions have been fetched. |
| */ |
| public interface WebsitePermissionsCallback { |
| void onWebsitePermissionsAvailable( |
| Map<String, Set<Website>> sitesByOrigin, Map<String, Set<Website>> sitesByHost); |
| } |
| |
| // This is a 1 <--> 1..N mapping between origin and Website. |
| // TODO(mvanouwerkerk): The Website class has no equals or hashCode methods so storing them in |
| // a HashSet is really confusing to readers of this code. There is no deduplication at all. |
| private final Map<String, Set<Website>> mSitesByOrigin = new HashMap<>(); |
| |
| // This is a 1 <--> 1..N mapping between host and Website. |
| // TODO(mvanouwerkerk): The Website class has no equals or hashCode methods so storing them in |
| // a HashSet is really confusing to readers of this code. There is no deduplication at all. |
| private final Map<String, Set<Website>> mSitesByHost = new HashMap<>(); |
| |
| // The callback to run when the permissions have been fetched. |
| private final WebsitePermissionsCallback mCallback; |
| |
| /** |
| * @param callback The callback to run when the fetch is complete. |
| */ |
| public WebsitePermissionsFetcher(WebsitePermissionsCallback callback) { |
| mCallback = callback; |
| } |
| |
| /** |
| * Fetches preferences for all sites that have them. |
| * TODO(mvanouwerkerk): Add an argument |url| to only fetch permissions for |
| * sites from the same origin as that of |url| - https://crbug.com/459222. |
| */ |
| public void fetchAllPreferences() { |
| TaskQueue queue = new TaskQueue(); |
| // Populate features from more specific to less specific. |
| // Geolocation lookup permission is per-origin and per-embedder. |
| queue.add(new GeolocationInfoFetcher()); |
| // Midi sysex access permission is per-origin and per-embedder. |
| queue.add(new MidiInfoFetcher()); |
| // Cookies are stored per-origin. |
| queue.add(new CookieInfoFetcher()); |
| // Fullscreen are stored per-origin. |
| queue.add(new FullscreenInfoFetcher()); |
| // Keygen permissions are per-origin. |
| queue.add(new KeygenInfoFetcher()); |
| // Local storage info is per-origin. |
| queue.add(new LocalStorageInfoFetcher()); |
| // Website storage is per-host. |
| queue.add(new WebStorageInfoFetcher()); |
| // Popup exceptions are host-based patterns (unless we start |
| // synchronizing popup exceptions with desktop Chrome.) |
| queue.add(new PopupExceptionInfoFetcher()); |
| // JavaScript exceptions are host-based patterns. |
| queue.add(new JavaScriptExceptionInfoFetcher()); |
| // Protected media identifier permission is per-origin and per-embedder. |
| queue.add(new ProtectedMediaIdentifierInfoFetcher()); |
| // Push notification permission is per-origin and per-embedder. |
| queue.add(new PushNotificationInfoFetcher()); |
| // Camera capture permission is per-origin and per-embedder. |
| queue.add(new CameraCaptureInfoFetcher()); |
| // Micropohone capture permission is per-origin and per-embedder. |
| queue.add(new MicrophoneCaptureInfoFetcher()); |
| queue.add(new PermissionsAvailableCallbackRunner()); |
| queue.next(); |
| } |
| |
| /** |
| * Fetches all preferences within a specific category. |
| * |
| * @param catgory A category to fetch. |
| */ |
| public void fetchPreferencesForCategory(SiteSettingsCategory category) { |
| if (category.showAllSites()) { |
| fetchAllPreferences(); |
| return; |
| } |
| |
| TaskQueue queue = new TaskQueue(); |
| // Populate features from more specific to less specific. |
| if (category.showGeolocationSites()) { |
| // Geolocation lookup permission is per-origin and per-embedder. |
| queue.add(new GeolocationInfoFetcher()); |
| } else if (category.showCookiesSites()) { |
| // Cookies are stored per-origin. |
| queue.add(new CookieInfoFetcher()); |
| } else if (category.showStorageSites()) { |
| // Local storage info is per-origin. |
| queue.add(new LocalStorageInfoFetcher()); |
| // Website storage is per-host. |
| queue.add(new WebStorageInfoFetcher()); |
| } else if (category.showFullscreenSites()) { |
| // Full screen is per-origin. |
| queue.add(new FullscreenInfoFetcher()); |
| } else if (category.showCameraSites()) { |
| // Camera capture permission is per-origin and per-embedder. |
| queue.add(new CameraCaptureInfoFetcher()); |
| } else if (category.showMicrophoneSites()) { |
| // Micropohone capture permission is per-origin and per-embedder. |
| queue.add(new MicrophoneCaptureInfoFetcher()); |
| } else if (category.showPopupSites()) { |
| // Popup exceptions are host-based patterns (unless we start |
| // synchronizing popup exceptions with desktop Chrome.) |
| queue.add(new PopupExceptionInfoFetcher()); |
| } else if (category.showJavaScriptSites()) { |
| // JavaScript exceptions are host-based patterns. |
| queue.add(new JavaScriptExceptionInfoFetcher()); |
| } else if (category.showNotificationsSites()) { |
| // Push notification permission is per-origin and per-embedder. |
| queue.add(new PushNotificationInfoFetcher()); |
| } else if (category.showProtectedMediaSites()) { |
| // Protected media identifier permission is per-origin and per-embedder. |
| queue.add(new ProtectedMediaIdentifierInfoFetcher()); |
| } |
| queue.add(new PermissionsAvailableCallbackRunner()); |
| queue.next(); |
| } |
| |
| private Website createSiteByOriginAndHost(WebsiteAddress address) { |
| String origin = address.getOrigin(); |
| String host = address.getHost(); |
| Website site = new Website(address); |
| if (!mSitesByOrigin.containsKey(origin)) mSitesByOrigin.put(origin, new HashSet<Website>()); |
| mSitesByOrigin.get(origin).add(site); |
| if (!mSitesByHost.containsKey(host)) mSitesByHost.put(host, new HashSet<Website>()); |
| mSitesByHost.get(host).add(site); |
| return site; |
| } |
| |
| private Set<Website> findOrCreateSitesByOrigin(WebsiteAddress address) { |
| String origin = address.getOrigin(); |
| if (!mSitesByOrigin.containsKey(origin)) createSiteByOriginAndHost(address); |
| return mSitesByOrigin.get(origin); |
| } |
| |
| private Set<Website> findOrCreateSitesByHost(WebsiteAddress address) { |
| String host = address.getHost(); |
| if (!mSitesByHost.containsKey(host)) { |
| mSitesByHost.put(host, new HashSet<Website>()); |
| mSitesByHost.get(host).add(new Website(address)); |
| } |
| return mSitesByHost.get(host); |
| } |
| |
| /** |
| * A single task in the WebsitePermissionsFetcher task queue. We need fetching of features to be |
| * serialized, as we need to have all the origins in place prior to populating the hosts. |
| */ |
| private abstract class Task { |
| /** Override this method to implement a synchronous task. */ |
| void run() {} |
| |
| /** |
| * Override this method to implement an asynchronous task. Call queue.next() once execution |
| * is complete. |
| */ |
| void runAsync(TaskQueue queue) { |
| run(); |
| queue.next(); |
| } |
| } |
| |
| /** |
| * A queue used to store the sequence of tasks to run to fetch the website preferences. Each |
| * task is run sequentially, and some of the tasks may run asynchronously. |
| */ |
| private static class TaskQueue extends LinkedList<Task> { |
| void next() { |
| if (!isEmpty()) removeFirst().runAsync(this); |
| } |
| } |
| |
| private class GeolocationInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (GeolocationInfo info : WebsitePreferenceBridge.getGeolocationInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setGeolocationInfo(info); |
| } |
| } |
| } |
| |
| private class MidiInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (MidiInfo info : WebsitePreferenceBridge.getMidiInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setMidiInfo(info); |
| } |
| } |
| } |
| |
| private class PopupExceptionInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (ContentSettingException exception : |
| WebsitePreferenceBridge.getContentSettingsExceptions( |
| ContentSettingsType.CONTENT_SETTINGS_TYPE_POPUPS)) { |
| // The pattern "*" represents the default setting, not a |
| // specific website. |
| if (exception.getPattern().equals("*")) continue; |
| WebsiteAddress address = WebsiteAddress.create(exception.getPattern()); |
| if (address == null) continue; |
| Set<Website> sites = findOrCreateSitesByHost(address); |
| for (Website site : sites) { |
| site.setPopupException(exception); |
| } |
| } |
| } |
| } |
| |
| private class JavaScriptExceptionInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (ContentSettingException exception |
| : WebsitePreferenceBridge.getContentSettingsExceptions( |
| ContentSettingsType.CONTENT_SETTINGS_TYPE_JAVASCRIPT)) { |
| // The pattern "*" represents the default setting, not a specific website. |
| if (exception.getPattern().equals("*")) continue; |
| WebsiteAddress address = WebsiteAddress.create(exception.getPattern()); |
| if (address == null) continue; |
| Set<Website> sites = findOrCreateSitesByHost(address); |
| for (Website site : sites) { |
| site.setJavaScriptException(exception); |
| } |
| } |
| } |
| } |
| |
| private class KeygenInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (KeygenInfo info : WebsitePreferenceBridge.getKeygenInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setKeygenInfo(info); |
| } |
| } |
| } |
| |
| private class CookieInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (CookieInfo info : WebsitePreferenceBridge.getCookieInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setCookieInfo(info); |
| } |
| } |
| } |
| |
| /** |
| * Class for fetching the fullscreen information. |
| */ |
| private class FullscreenInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (FullscreenInfo info : WebsitePreferenceBridge.getFullscreenInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setFullscreenInfo(info); |
| } |
| } |
| } |
| |
| private class LocalStorageInfoFetcher extends Task { |
| @Override |
| public void runAsync(final TaskQueue queue) { |
| WebsitePreferenceBridge.fetchLocalStorageInfo( |
| new WebsitePreferenceBridge.LocalStorageInfoReadyCallback() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public void onLocalStorageInfoReady(HashMap map) { |
| for (Object o : map.entrySet()) { |
| Map.Entry<String, LocalStorageInfo> entry = |
| (Map.Entry<String, LocalStorageInfo>) o; |
| WebsiteAddress address = WebsiteAddress.create(entry.getKey()); |
| if (address == null) continue; |
| Set<Website> sites = findOrCreateSitesByOrigin(address); |
| for (Website site : sites) { |
| site.setLocalStorageInfo(entry.getValue()); |
| } |
| } |
| queue.next(); |
| } |
| }); |
| } |
| } |
| |
| private class WebStorageInfoFetcher extends Task { |
| @Override |
| public void runAsync(final TaskQueue queue) { |
| WebsitePreferenceBridge.fetchStorageInfo( |
| new WebsitePreferenceBridge.StorageInfoReadyCallback() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public void onStorageInfoReady(ArrayList array) { |
| ArrayList<StorageInfo> infoArray = array; |
| for (StorageInfo info : infoArray) { |
| WebsiteAddress address = WebsiteAddress.create(info.getHost()); |
| if (address == null) continue; |
| Set<Website> sites = findOrCreateSitesByHost(address); |
| for (Website site : sites) { |
| site.addStorageInfo(info); |
| } |
| } |
| queue.next(); |
| } |
| }); |
| } |
| } |
| |
| private class ProtectedMediaIdentifierInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (ProtectedMediaIdentifierInfo info : |
| WebsitePreferenceBridge.getProtectedMediaIdentifierInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setProtectedMediaIdentifierInfo(info); |
| } |
| } |
| } |
| |
| private class PushNotificationInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (PushNotificationInfo info : WebsitePreferenceBridge.getPushNotificationInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setPushNotificationInfo(info); |
| } |
| } |
| } |
| |
| private class CameraCaptureInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (CameraInfo info : WebsitePreferenceBridge.getCameraInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setCameraInfo(info); |
| } |
| } |
| } |
| |
| private class MicrophoneCaptureInfoFetcher extends Task { |
| @Override |
| public void run() { |
| for (MicrophoneInfo info : WebsitePreferenceBridge.getMicrophoneInfo()) { |
| WebsiteAddress address = WebsiteAddress.create(info.getOrigin()); |
| if (address == null) continue; |
| createSiteByOriginAndHost(address).setMicrophoneInfo(info); |
| } |
| } |
| } |
| |
| private class PermissionsAvailableCallbackRunner extends Task { |
| @Override |
| public void run() { |
| mCallback.onWebsitePermissionsAvailable(mSitesByOrigin, mSitesByHost); |
| } |
| } |
| } |