1/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2/* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6/* eslint no-unused-vars: ["error", {args: "none"}] */ 7 8var EXPORTED_SYMBOLS = ["PopupBlockingChild"]; 9 10// The maximum number of popup information we'll send to the parent. 11const MAX_SENT_POPUPS = 15; 12 13const { XPCOMUtils } = ChromeUtils.import( 14 "resource://gre/modules/XPCOMUtils.jsm" 15); 16 17class PopupBlockingChild extends JSWindowActorChild { 18 constructor() { 19 super(); 20 this.weakDocStates = new WeakMap(); 21 } 22 23 actorCreated() { 24 this.contentWindow.addEventListener("pageshow", this); 25 } 26 27 willDestroy() { 28 this.contentWindow.removeEventListener("pageshow", this); 29 } 30 31 /** 32 * Returns the state for the current document referred to via 33 * this.document. If no such state exists, creates it, stores it 34 * and returns it. 35 */ 36 get docState() { 37 let state = this.weakDocStates.get(this.document); 38 if (!state) { 39 state = { 40 popupData: [], 41 }; 42 this.weakDocStates.set(this.document, state); 43 } 44 45 return state; 46 } 47 48 receiveMessage(msg) { 49 switch (msg.name) { 50 case "UnblockPopup": { 51 let i = msg.data.index; 52 let state = this.docState; 53 let popupData = state.popupData[i]; 54 if (popupData) { 55 let dwi = popupData.requestingWindow; 56 57 // If we have a requesting window and the requesting document is 58 // still the current document, open the popup. 59 if (dwi && dwi.document == popupData.requestingDocument) { 60 dwi.open( 61 popupData.popupWindowURISpec, 62 popupData.popupWindowName, 63 popupData.popupWindowFeatures 64 ); 65 } 66 } 67 break; 68 } 69 70 case "GetBlockedPopupList": { 71 let state = this.docState; 72 let length = Math.min(state.popupData.length, MAX_SENT_POPUPS); 73 74 let result = []; 75 76 for (let i = 0; i < length; ++i) { 77 let popup = state.popupData[i]; 78 79 let popupWindowURISpec = popup.popupWindowURISpec; 80 81 if (this.contentWindow.location.href == popupWindowURISpec) { 82 popupWindowURISpec = "<self>"; 83 } else { 84 // Limit 500 chars to be sent because the URI will be cropped 85 // by the UI anyway, and data: URIs can be significantly larger. 86 popupWindowURISpec = popupWindowURISpec.substring(0, 500); 87 } 88 89 result.push({ 90 popupWindowURISpec, 91 }); 92 } 93 94 return result; 95 } 96 } 97 98 return null; 99 } 100 101 handleEvent(event) { 102 switch (event.type) { 103 case "DOMPopupBlocked": 104 this.onPopupBlocked(event); 105 break; 106 case "pageshow": { 107 this.onPageShow(event); 108 break; 109 } 110 } 111 } 112 113 onPopupBlocked(event) { 114 if (event.target != this.document) { 115 return; 116 } 117 118 let state = this.docState; 119 120 // Avoid spamming the parent process with too many blocked popups. 121 if (state.popupData.length >= PopupBlockingChild.maxReportedPopups) { 122 return; 123 } 124 125 let popup = { 126 popupWindowURISpec: event.popupWindowURI 127 ? event.popupWindowURI.spec 128 : "about:blank", 129 popupWindowFeatures: event.popupWindowFeatures, 130 popupWindowName: event.popupWindowName, 131 requestingWindow: event.requestingWindow, 132 requestingDocument: event.requestingWindow.document, 133 }; 134 135 state.popupData.push(popup); 136 this.updateBlockedPopups(true); 137 } 138 139 onPageShow(event) { 140 if (event.target != this.document) { 141 return; 142 } 143 144 this.updateBlockedPopups(false); 145 } 146 147 updateBlockedPopups(shouldNotify) { 148 this.sendAsyncMessage("UpdateBlockedPopups", { 149 shouldNotify, 150 count: this.docState.popupData.length, 151 }); 152 } 153} 154 155XPCOMUtils.defineLazyPreferenceGetter( 156 PopupBlockingChild, 157 "maxReportedPopups", 158 "privacy.popups.maxReported" 159); 160