1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/printing/background_printing_manager.h"
6
7 #include "base/stl_util.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/printing/print_job.h"
10 #include "chrome/browser/printing/print_preview_dialog_controller.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/notification_details.h"
13 #include "content/public/browser/notification_source.h"
14 #include "content/public/browser/render_view_host.h"
15 #include "content/public/browser/web_contents.h"
16 #include "content/public/browser/web_contents_delegate.h"
17
18 using content::BrowserThread;
19 using content::WebContents;
20
21 namespace printing {
22
BackgroundPrintingManager()23 BackgroundPrintingManager::BackgroundPrintingManager() {
24 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
25 }
26
~BackgroundPrintingManager()27 BackgroundPrintingManager::~BackgroundPrintingManager() {
28 DCHECK(CalledOnValidThread());
29 // The might be some WebContentses still in |printing_contents_set_| at this
30 // point. E.g. when the last remaining tab closes and there is still a print
31 // preview WebContents trying to print. In which case it will fail to print.
32 // TODO(thestig): Handle this case better.
33 }
34
OwnPrintPreviewDialog(WebContents * preview_dialog)35 void BackgroundPrintingManager::OwnPrintPreviewDialog(
36 WebContents* preview_dialog) {
37 DCHECK(CalledOnValidThread());
38 DCHECK(PrintPreviewDialogController::IsPrintPreviewDialog(preview_dialog));
39 CHECK(!HasPrintPreviewDialog(preview_dialog));
40
41 printing_contents_set_.insert(preview_dialog);
42
43 content::Source<WebContents> preview_source(preview_dialog);
44 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_RELEASED, preview_source);
45
46 // OwnInitiatorWebContents() may have already added this notification.
47 if (!registrar_.IsRegistered(this,
48 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, preview_source)) {
49 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
50 preview_source);
51 }
52
53 // If a WebContents that is printing crashes, the user cannot destroy it since
54 // it is hidden. Thus listen for crashes here and delete it.
55 //
56 // Multiple sites may share the same RenderProcessHost, so check if this
57 // notification has already been added.
58 content::Source<content::RenderProcessHost> rph_source(
59 preview_dialog->GetRenderProcessHost());
60 if (!registrar_.IsRegistered(this,
61 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) {
62 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
63 rph_source);
64 }
65
66 // Activate the initiator.
67 PrintPreviewDialogController* dialog_controller =
68 PrintPreviewDialogController::GetInstance();
69 if (!dialog_controller)
70 return;
71 WebContents* initiator = dialog_controller->GetInitiator(preview_dialog);
72 if (!initiator)
73 return;
74 initiator->GetDelegate()->ActivateContents(initiator);
75 }
76
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)77 void BackgroundPrintingManager::Observe(
78 int type,
79 const content::NotificationSource& source,
80 const content::NotificationDetails& details) {
81 if (type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED) {
82 OnRendererProcessClosed(
83 content::Source<content::RenderProcessHost>(source).ptr());
84 } else if (type == chrome::NOTIFICATION_PRINT_JOB_RELEASED) {
85 OnPrintJobReleased(content::Source<WebContents>(source).ptr());
86 } else {
87 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
88 OnWebContentsDestroyed(content::Source<WebContents>(source).ptr());
89 }
90 }
91
OnRendererProcessClosed(content::RenderProcessHost * rph)92 void BackgroundPrintingManager::OnRendererProcessClosed(
93 content::RenderProcessHost* rph) {
94 WebContentsSet printing_contents_pending_deletion_set;
95 WebContentsSet::const_iterator it;
96 for (it = begin(); it != end(); ++it) {
97 WebContents* preview_contents = *it;
98 if (preview_contents->GetRenderProcessHost() == rph) {
99 printing_contents_pending_deletion_set.insert(preview_contents);
100 }
101 }
102 for (it = printing_contents_pending_deletion_set.begin();
103 it != printing_contents_pending_deletion_set.end();
104 ++it) {
105 DeletePreviewContents(*it);
106 }
107 }
108
OnPrintJobReleased(WebContents * preview_contents)109 void BackgroundPrintingManager::OnPrintJobReleased(
110 WebContents* preview_contents) {
111 DeletePreviewContents(preview_contents);
112 }
113
OnWebContentsDestroyed(WebContents * preview_contents)114 void BackgroundPrintingManager::OnWebContentsDestroyed(
115 WebContents* preview_contents) {
116 // Always need to remove this notification since the WebContents is gone.
117 content::Source<WebContents> preview_source(preview_contents);
118 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
119 preview_source);
120
121 if (!HasPrintPreviewDialog(preview_contents)) {
122 NOTREACHED();
123 return;
124 }
125
126 // Remove NOTIFICATION_RENDERER_PROCESS_CLOSED if |preview_contents| is the
127 // last WebContents associated with |rph|.
128 bool shared_rph =
129 (HasSharedRenderProcessHost(printing_contents_set_, preview_contents) ||
130 HasSharedRenderProcessHost(printing_contents_pending_deletion_set_,
131 preview_contents));
132 if (!shared_rph) {
133 content::RenderProcessHost* rph = preview_contents->GetRenderProcessHost();
134 registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
135 content::Source<content::RenderProcessHost>(rph));
136 }
137
138 // Remove other notifications and remove the WebContents from its
139 // WebContentsSet.
140 if (printing_contents_set_.erase(preview_contents) == 1) {
141 registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_RELEASED,
142 preview_source);
143 } else {
144 // DeletePreviewContents already deleted the notification.
145 printing_contents_pending_deletion_set_.erase(preview_contents);
146 }
147 }
148
DeletePreviewContents(WebContents * preview_contents)149 void BackgroundPrintingManager::DeletePreviewContents(
150 WebContents* preview_contents) {
151 registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_RELEASED,
152 content::Source<WebContents>(preview_contents));
153 printing_contents_set_.erase(preview_contents);
154 printing_contents_pending_deletion_set_.insert(preview_contents);
155 base::MessageLoop::current()->DeleteSoon(FROM_HERE, preview_contents);
156 }
157
HasSharedRenderProcessHost(const WebContentsSet & set,WebContents * preview_contents)158 bool BackgroundPrintingManager::HasSharedRenderProcessHost(
159 const WebContentsSet& set, WebContents* preview_contents) {
160 content::RenderProcessHost* rph = preview_contents->GetRenderProcessHost();
161 for (WebContentsSet::const_iterator it = set.begin(); it != set.end(); ++it) {
162 WebContents* iter_contents = *it;
163 if (iter_contents == preview_contents)
164 continue;
165 if (iter_contents->GetRenderProcessHost() == rph)
166 return true;
167 }
168 return false;
169 }
170
171 BackgroundPrintingManager::WebContentsSet::const_iterator
begin()172 BackgroundPrintingManager::begin() {
173 return printing_contents_set_.begin();
174 }
175
176 BackgroundPrintingManager::WebContentsSet::const_iterator
end()177 BackgroundPrintingManager::end() {
178 return printing_contents_set_.end();
179 }
180
HasPrintPreviewDialog(WebContents * preview_dialog)181 bool BackgroundPrintingManager::HasPrintPreviewDialog(
182 WebContents* preview_dialog) {
183 return (ContainsKey(printing_contents_set_, preview_dialog) ||
184 ContainsKey(printing_contents_pending_deletion_set_, preview_dialog));
185 }
186
187 } // namespace printing
188