summaryrefslogtreecommitdiffstats
path: root/chrome/browser/printing
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/printing')
-rw-r--r--chrome/browser/printing/print_dialog_cloud.cc480
-rw-r--r--chrome/browser/printing/print_dialog_cloud.h38
-rw-r--r--chrome/browser/printing/print_dialog_cloud_internal.h172
-rw-r--r--chrome/browser/printing/print_dialog_cloud_uitest.cc176
-rw-r--r--chrome/browser/printing/print_dialog_cloud_unittest.cc189
5 files changed, 1055 insertions, 0 deletions
diff --git a/chrome/browser/printing/print_dialog_cloud.cc b/chrome/browser/printing/print_dialog_cloud.cc
new file mode 100644
index 0000000..02385d2
--- /dev/null
+++ b/chrome/browser/printing/print_dialog_cloud.cc
@@ -0,0 +1,480 @@
+// Copyright (c) 2010 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.
+
+#include "chrome/browser/printing/print_dialog_cloud.h"
+#include "chrome/browser/printing/print_dialog_cloud_internal.h"
+
+#include "app/l10n_util.h"
+#include "base/base64.h"
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/dom_ui/dom_ui.h"
+#include "chrome/browser/dom_ui/dom_ui_util.h"
+#include "chrome/browser/dom_ui/html_dialog_ui.h"
+#include "chrome/browser/debugger/devtools_manager.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/url_constants.h"
+
+#include "grit/generated_resources.h"
+
+// This module implements the UI support in Chrome for cloud printing.
+// This means hosting a dialog containing HTML/JavaScript and using
+// the published cloud print user interface integration APIs to get
+// page setup settings from the dialog contents and provide the
+// generated print PDF to the dialog contents for uploading to the
+// cloud print service.
+
+// Currently, the flow between these classes is as follows:
+
+// PrintDialogCloud::CreatePrintDialogForPdf is called from
+// resource_message_filter_gtk.cc once the renderer has informed the
+// renderer host that PDF generation into the renderer host provided
+// temp file has been completed. That call is on the IO thread.
+// That, in turn, hops over to the UI thread to create an instance of
+// PrintDialogCloud.
+
+// The constructor for PrintDialogCloud creates a
+// CloudPrintHtmlDialogDelegate and asks the current active browser to
+// show an HTML dialog using that class as the delegate. That class
+// hands in the kCloudPrintResourcesURL as the URL to visit. That is
+// recognized by the GetDOMUIFactoryFunction as a signal to create an
+// ExternalHtmlDialogUI.
+
+// CloudPrintHtmlDialogDelegate also temporarily owns a
+// CloudPrintFlowHandler, a class which is responsible for the actual
+// interactions with the dialog contents, including handing in the PDF
+// print data and getting any page setup parameters that the dialog
+// contents provides. As part of bringing up the dialog,
+// HtmlDialogUI::RenderViewCreated is called (an override of
+// DOMUI::RenderViewCreated). That routine, in turn, calls the
+// delegate's GetDOMMessageHandlers routine, at which point the
+// ownership of the CloudPrintFlowHandler is handed over. A pointer
+// to the flow handler is kept to facilitate communication back and
+// forth between the two classes.
+
+// The DOMUI continues dialog bring-up, calling
+// CloudPrintFlowHandler::RegisterMessages. This is where the
+// additional object model capabilities are registered for the dialog
+// contents to use. It is also at this time that capabilities for the
+// dialog contents are adjusted to allow the dialog contents to close
+// the window. In addition, the pending URL is redirected to the
+// actual cloud print service URL. The flow controller also registers
+// for notification of when the dialog contents finish loading, which
+// is currently used to send the PDF data to the dialog contents.
+
+// In order to send the PDF data to the dialog contents, the flow
+// handler uses a CloudPrintDataSender. It creates one, letting it
+// know the name of the temporary file containing the PDF data, and
+// posts the task of reading the file
+// (CloudPrintDataSender::ReadPrintDataFile) to the file thread. That
+// routine reads in the file, and then hops over to the IO thread to
+// send that data to the dialog contents.
+
+// When the dialog contents are finished (by either being cancelled or
+// hitting the print button), the delegate is notified, and responds
+// that the dialog should be closed, at which point things are torn
+// down and released.
+
+// TODO(scottbyer):
+// http://code.google.com/p/chromium/issues/detail?id=44093 The
+// high-level flow (where the PDF data is generated before even
+// bringing up the dialog) isn't what we want.
+
+
+namespace internal_cloud_print_helpers {
+
+// TODO(scottbyer): Replace with the real public URL when we have one.
+// That, and get it into the profile instead of as a hardwired
+// constant.
+const char* const kCloudPrintDialogUrl =
+ "http://placeholderurl.ned/printing/client/dialog.html";
+
+bool GetRealOrInt(const DictionaryValue& dictionary,
+ const std::wstring& path,
+ double* out_value) {
+ if (!dictionary.GetReal(path, out_value)) {
+ int int_value = 0;
+ if (!dictionary.GetInteger(path, &int_value))
+ return false;
+ *out_value = int_value;
+ }
+ return true;
+}
+
+// From the JSON parsed value, get the entries for the page setup
+// parameters.
+bool GetPageSetupParameters(const std::string& json,
+ ViewMsg_Print_Params& parameters) {
+ scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
+ DLOG_IF(ERROR, (!parsed_value.get() ||
+ !parsed_value->IsType(Value::TYPE_DICTIONARY)))
+ << "PageSetup call didn't have expected contents";
+ if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+
+ bool result = true;
+ DictionaryValue* params = static_cast<DictionaryValue*>(parsed_value.get());
+ result &= GetRealOrInt(*params, L"dpi", &parameters.dpi);
+ result &= GetRealOrInt(*params, L"min_shrink", &parameters.min_shrink);
+ result &= GetRealOrInt(*params, L"max_shrink", &parameters.max_shrink);
+ result &= params->GetBoolean(L"selection_only", &parameters.selection_only);
+ return result;
+}
+
+void CloudPrintDataSenderHelper::CallJavascriptFunction(
+ const std::wstring& function_name) {
+ dom_ui_->CallJavascriptFunction(function_name);
+}
+
+void CloudPrintDataSenderHelper::CallJavascriptFunction(
+ const std::wstring& function_name, const Value& arg) {
+ dom_ui_->CallJavascriptFunction(function_name, arg);
+}
+
+void CloudPrintDataSenderHelper::CallJavascriptFunction(
+ const std::wstring& function_name, const Value& arg1, const Value& arg2) {
+ dom_ui_->CallJavascriptFunction(function_name, arg1, arg2);
+}
+
+// Clears out the pointer we're using to communicate. Either routine is
+// potentially expensive enough that stopping whatever is in progress
+// is worth it.
+void CloudPrintDataSender::CancelPrintDataFile() {
+ AutoLock lock(lock_);
+ // We don't own helper, it was passed in to us, so no need to
+ // delete, just let it go.
+ helper_ = NULL;
+}
+
+// Grab the raw PDF file contents and massage them into shape for
+// sending to the dialog contents (and up to the cloud print server)
+// by encoding it and prefixing it with the appropriate mime type.
+// Once that is done, kick off the next part of the task on the IO
+// thread.
+void CloudPrintDataSender::ReadPrintDataFile(const FilePath& path_to_pdf) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+ int64 file_size = 0;
+ if (file_util::GetFileSize(path_to_pdf, &file_size) && file_size != 0) {
+ std::string file_data;
+ if (file_size < kuint32max) {
+ file_data.reserve(static_cast<unsigned int>(file_size));
+ } else {
+ DLOG(WARNING) << " print data file too large to reserve space";
+ }
+ if (helper_ && file_util::ReadFileToString(path_to_pdf, &file_data)) {
+ std::string base64_data;
+ base::Base64Encode(file_data, &base64_data);
+ std::string header("data:application/pdf;base64,");
+ base64_data.insert(0, header);
+ scoped_ptr<StringValue> new_data(new StringValue(base64_data));
+ print_data_.swap(new_data);
+ ChromeThread::PostTask(ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ this,
+ &CloudPrintDataSender::SendPrintDataFile));
+ }
+ }
+}
+
+// We have the data in hand that needs to be pushed into the dialog
+// contents; do so from the IO thread.
+
+// TODO(scottbyer): If the print data ends up being larger than the
+// upload limit (currently 10MB), what we need to do is upload that
+// large data to google docs and set the URL in the printing
+// JavaScript to that location, and make sure it gets deleted when not
+// needed. - 4/1/2010
+void CloudPrintDataSender::SendPrintDataFile() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+ AutoLock lock(lock_);
+ if (helper_ && print_data_.get()) {
+ // TODO(scottbyer) - fill this in with the title or URL of the
+ // original page.
+ StringValue title("Chrome Print Test");
+
+ // Send the print data to the dialog contents. The JavaScript
+ // function is a preliminary API for prototyping purposes and is
+ // subject to change.
+ const_cast<CloudPrintDataSenderHelper*>(helper_)->CallJavascriptFunction(
+ L"printApp._printDataUrl", *print_data_, title);
+ }
+}
+
+
+void CloudPrintFlowHandler::SetDialogDelegate(
+ CloudPrintHtmlDialogDelegate* delegate) {
+ // Even if setting a new dom_ui, it means any previous task needs
+ // to be cancelled, it's now invalid.
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ CancelAnyRunningTask();
+ dialog_delegate_ = delegate;
+}
+
+// Cancels any print data sender we have in flight and removes our
+// reference to it, so when the task that is calling it finishes and
+// removes it's reference, it goes away.
+void CloudPrintFlowHandler::CancelAnyRunningTask() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ if (print_data_sender_.get()) {
+ print_data_sender_->CancelPrintDataFile();
+ print_data_sender_ = NULL;
+ }
+}
+
+
+void CloudPrintFlowHandler::RegisterMessages() {
+ if (!dom_ui_)
+ return;
+
+ // TODO(scottbyer) - This is where we will register messages for the
+ // UI JS to use. Needed: Call to update page setup parameters.
+ dom_ui_->RegisterMessageCallback(
+ "ShowDebugger",
+ NewCallback(this, &CloudPrintFlowHandler::HandleShowDebugger));
+ dom_ui_->RegisterMessageCallback(
+ "SendPrintData",
+ NewCallback(this, &CloudPrintFlowHandler::HandleSendPrintData));
+ dom_ui_->RegisterMessageCallback(
+ "SetPageParameters",
+ NewCallback(this, &CloudPrintFlowHandler::HandleSetPageParameters));
+
+ if (dom_ui_->tab_contents()) {
+ // Also, take the opportunity to set some (minimal) additional
+ // script permissions required for the web UI.
+
+ // TODO(scottbyer): learn how to make sure we're talking to the
+ // right web site first.
+ RenderViewHost* rvh = dom_ui_->tab_contents()->render_view_host();
+ if (rvh && rvh->delegate()) {
+ WebPreferences webkit_prefs = rvh->delegate()->GetWebkitPrefs();
+ webkit_prefs.allow_scripts_to_close_windows = true;
+ rvh->UpdateWebPreferences(webkit_prefs);
+ }
+
+ // Register for appropriate notifications, and re-direct the URL
+ // to the real server URL, now that we've gotten an HTML dialog
+ // going.
+ NavigationController* controller = &dom_ui_->tab_contents()->controller();
+ NavigationEntry* pending_entry = controller->pending_entry();
+ if (pending_entry)
+ pending_entry->set_url(GURL(kCloudPrintDialogUrl));
+ registrar_.Add(this, NotificationType::LOAD_STOP,
+ Source<NavigationController>(controller));
+ }
+}
+
+void CloudPrintFlowHandler::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::LOAD_STOP) {
+ // Choose one or the other. If you need to debug, bring up the
+ // debugger. You can then use the various chrome.send()
+ // registrations above to kick of the various function calls,
+ // including chrome.send("SendPrintData") in the javaScript
+ // console and watch things happen with:
+ // HandleShowDebugger(NULL);
+ HandleSendPrintData(NULL);
+ }
+}
+
+void CloudPrintFlowHandler::HandleShowDebugger(const Value* value) {
+ ShowDebugger();
+}
+
+void CloudPrintFlowHandler::ShowDebugger() {
+ if (dom_ui_) {
+ RenderViewHost* rvh = dom_ui_->tab_contents()->render_view_host();
+ if (rvh)
+ DevToolsManager::GetInstance()->OpenDevToolsWindow(rvh);
+ }
+}
+
+scoped_refptr<CloudPrintDataSender>
+CloudPrintFlowHandler::CreateCloudPrintDataSender() {
+ DCHECK(dom_ui_);
+ print_data_helper_.reset(new CloudPrintDataSenderHelper(dom_ui_));
+ return new CloudPrintDataSender(print_data_helper_.get());
+}
+
+void CloudPrintFlowHandler::HandleSendPrintData(const Value* value) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ // This will cancel any ReadPrintDataFile() or SendPrintDataFile()
+ // requests in flight (this is anticipation of when setting page
+ // setup parameters becomes asynchronous and may be set while some
+ // data is in flight). Then we can clear out the print data.
+ CancelAnyRunningTask();
+ if (dom_ui_) {
+ print_data_sender_ = CreateCloudPrintDataSender();
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(
+ print_data_sender_.get(),
+ &CloudPrintDataSender::ReadPrintDataFile,
+ path_to_pdf_));
+ }
+}
+
+void CloudPrintFlowHandler::HandleSetPageParameters(const Value* value) {
+ std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(value));
+ if (json.empty())
+ return;
+
+ // These are backstop default values - 72 dpi to match the screen,
+ // 8.5x11 inch paper with margins subtracted (1/4 inch top, left,
+ // right and 0.56 bottom), and the min page shrink and max page
+ // shrink values appear all over the place with no explanation.
+
+ // TODO(scottbyer): Get a Linux/ChromeOS edge for PrintSettings
+ // working so that we can get the default values from there. Fix up
+ // PrintWebViewHelper to do the same.
+ const int kDPI = 72;
+ const int kWidth = static_cast<int>((8.5-0.25-0.25)*kDPI);
+ const int kHeight = static_cast<int>((11-0.25-0.56)*kDPI);
+ const double kMinPageShrink = 1.25;
+ const double kMaxPageShrink = 2.0;
+
+ ViewMsg_Print_Params default_settings;
+ default_settings.printable_size = gfx::Size(kWidth, kHeight);
+ default_settings.dpi = kDPI;
+ default_settings.min_shrink = kMinPageShrink;
+ default_settings.max_shrink = kMaxPageShrink;
+ default_settings.desired_dpi = kDPI;
+ default_settings.document_cookie = 0;
+ default_settings.selection_only = false;
+
+ if (!GetPageSetupParameters(json, default_settings)) {
+ NOTREACHED();
+ return;
+ }
+
+ // TODO(scottbyer) - Here is where we would kick the originating
+ // renderer thread with these new parameters in order to get it to
+ // re-generate the PDF and hand it back to us. window.print() is
+ // currently synchronous, so there's a lot of work to do to get to
+ // that point.
+}
+
+CloudPrintHtmlDialogDelegate::CloudPrintHtmlDialogDelegate(
+ const FilePath& path_to_pdf,
+ int width, int height,
+ const std::string& json_arguments)
+ : flow_handler_(new CloudPrintFlowHandler(path_to_pdf)),
+ owns_flow_handler_(true) {
+ Init(width, height, json_arguments);
+}
+
+CloudPrintHtmlDialogDelegate::CloudPrintHtmlDialogDelegate(
+ CloudPrintFlowHandler* flow_handler,
+ int width, int height,
+ const std::string& json_arguments)
+ : flow_handler_(flow_handler),
+ owns_flow_handler_(false) {
+ Init(width, height, json_arguments);
+}
+
+void CloudPrintHtmlDialogDelegate::Init(
+ int width, int height, const std::string& json_arguments) {
+ // This information is needed to show the dialog HTML content.
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ std::string cloud_print_url(chrome::kCloudPrintResourcesURL);
+ params_.url = GURL(cloud_print_url);
+ params_.height = height;
+ params_.width = width;
+ params_.json_input = json_arguments;
+
+ flow_handler_->SetDialogDelegate(this);
+}
+
+CloudPrintHtmlDialogDelegate::~CloudPrintHtmlDialogDelegate() {
+ // If the flow_handler_ is about to outlive us because we don't own
+ // it anymore, we need to have it remove it's reference to us.
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ flow_handler_->SetDialogDelegate(NULL);
+ if (owns_flow_handler_) {
+ delete flow_handler_;
+ }
+}
+
+bool CloudPrintHtmlDialogDelegate::IsDialogModal() const {
+ return true;
+}
+
+std::wstring CloudPrintHtmlDialogDelegate::GetDialogTitle() const {
+ return l10n_util::GetString(IDS_CLOUD_PRINT_TITLE);
+}
+
+GURL CloudPrintHtmlDialogDelegate::GetDialogContentURL() const {
+ return params_.url;
+}
+
+void CloudPrintHtmlDialogDelegate::GetDOMMessageHandlers(
+ std::vector<DOMMessageHandler*>* handlers) const {
+ handlers->push_back(flow_handler_);
+ // We don't own flow_handler_ anymore, but it sticks around until at
+ // least right after OnDialogClosed() is called (and this object is
+ // destroyed).
+ owns_flow_handler_ = false;
+}
+
+void CloudPrintHtmlDialogDelegate::GetDialogSize(gfx::Size* size) const {
+ size->set_width(params_.width);
+ size->set_height(params_.height);
+}
+
+std::string CloudPrintHtmlDialogDelegate::GetDialogArgs() const {
+ return params_.json_input;
+}
+
+void CloudPrintHtmlDialogDelegate::OnDialogClosed(
+ const std::string& json_retval) {
+ delete this;
+}
+
+} // end of namespace internal_cloud_print_helpers
+
+// static, called on the IO thread. This is the main entry point into
+// creating the dialog.
+
+// TODO(scottbyer): The signature here will need to change as the
+// workflow through the printing code changes to allow for dynamically
+// changing page setup parameters while the dialog is active.
+void PrintDialogCloud::CreatePrintDialogForPdf(const FilePath& path_to_pdf) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
+
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableFunction(&PrintDialogCloud::CreateDialogImpl, path_to_pdf));
+}
+
+// static, called from the UI thread.
+void PrintDialogCloud::CreateDialogImpl(const FilePath& path_to_pdf) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ new PrintDialogCloud(path_to_pdf);
+}
+
+// Initialize the print dialog. Called on the UI thread.
+PrintDialogCloud::PrintDialogCloud(const FilePath& path_to_pdf)
+ : browser_(BrowserList::GetLastActive()) {
+
+ // TODO(scottbyer): Verify GAIA login valid, execute GAIA login if not (should
+ // be distilled out of bookmark sync.)
+
+ // TODO(scottbyer): Get the dialog width, height from the dialog
+ // contents, and take the screen size into account.
+ HtmlDialogUIDelegate* dialog_delegate =
+ new internal_cloud_print_helpers::CloudPrintHtmlDialogDelegate(
+ path_to_pdf, 500, 400, std::string());
+ browser_->BrowserShowHtmlDialog(dialog_delegate, NULL);
+}
+
+PrintDialogCloud::~PrintDialogCloud() {
+}
diff --git a/chrome/browser/printing/print_dialog_cloud.h b/chrome/browser/printing/print_dialog_cloud.h
new file mode 100644
index 0000000..7e8515b
--- /dev/null
+++ b/chrome/browser/printing/print_dialog_cloud.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_H_
+#define CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_H_
+
+#include "base/basictypes.h"
+
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+class Browser;
+class FilePath;
+namespace IPC {
+class Message;
+}
+
+class PrintDialogCloud {
+ public:
+ // Called on the IO thread.
+ static void CreatePrintDialogForPdf(const FilePath& path_to_pdf);
+
+ private:
+ FRIEND_TEST(PrintDialogCloudTest, HandlersRegistered);
+
+ explicit PrintDialogCloud(const FilePath& path_to_pdf);
+ ~PrintDialogCloud();
+
+ // Called as a task from the UI thread, creates an object instance
+ // to run the HTML/JS based print dialog for printing through the cloud.
+ static void CreateDialogImpl(const FilePath& path_to_pdf);
+
+ Browser* browser_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrintDialogCloud);
+};
+
+#endif // CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_H_
diff --git a/chrome/browser/printing/print_dialog_cloud_internal.h b/chrome/browser/printing/print_dialog_cloud_internal.h
new file mode 100644
index 0000000..1bde98e
--- /dev/null
+++ b/chrome/browser/printing/print_dialog_cloud_internal.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_INTERNAL_H_
+#define CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_INTERNAL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/lock.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/dom_ui/dom_ui.h"
+#include "chrome/browser/dom_ui/html_dialog_ui.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+
+class StringValue;
+
+namespace internal_cloud_print_helpers {
+
+// TODO(scottbyer): this should really be fetched from a profile
+// preference. 4/8/10
+extern const char* const kCloudPrintDialogUrl;
+
+// Small class to virtualize a few functions to aid with unit testing.
+class CloudPrintDataSenderHelper {
+ public:
+ explicit CloudPrintDataSenderHelper(DOMUI* dom_ui) : dom_ui_(dom_ui) {}
+ virtual ~CloudPrintDataSenderHelper() {}
+
+ // Virtualize the overrides of these three functions from DOMUI to
+ // facilitate unit testing.
+ virtual void CallJavascriptFunction(const std::wstring& function_name);
+ virtual void CallJavascriptFunction(const std::wstring& function_name,
+ const Value& arg);
+ virtual void CallJavascriptFunction(const std::wstring& function_name,
+ const Value& arg1,
+ const Value& arg2);
+
+ private:
+ DOMUI* dom_ui_;
+
+ DISALLOW_COPY_AND_ASSIGN(CloudPrintDataSenderHelper);
+};
+
+// Small helper class to get the print data loaded in from the PDF
+// file (on the FILE thread) and send it to the print dialog contents
+// (on the IO thread), allowing for cancellation.
+class CloudPrintDataSender
+ : public base::RefCountedThreadSafe<CloudPrintDataSender> {
+ public:
+ // The owner of this object is also expected to own and control the
+ // lifetime of the helper.
+ explicit CloudPrintDataSender(CloudPrintDataSenderHelper* helper)
+ : helper_(helper) {}
+
+ // Calls to read in the PDF file (on the FILE thread) then send that
+ // information to the dialog renderer (on the IO thread). We know
+ // that the dom_ui pointer lifetime will outlast us, so we should be
+ // good.
+ void ReadPrintDataFile(const FilePath& path_to_pdf);
+ void SendPrintDataFile();
+
+ // Cancels any ramining part of the task by clearing out the dom_ui
+ // helper_ ptr.
+ void CancelPrintDataFile();
+
+ private:
+ friend class base::RefCountedThreadSafe<CloudPrintDataSender>;
+ ~CloudPrintDataSender() {}
+
+ Lock lock_;
+ CloudPrintDataSenderHelper* volatile helper_;
+ scoped_ptr<StringValue> print_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(CloudPrintDataSender);
+};
+
+class CloudPrintHtmlDialogDelegate;
+
+// The CloudPrintFlowHandler connects the state machine (the UI delegate)
+// to the dialog backing HTML and JS by providing DOMMessageHandler
+// functions for the JS to use. This include refreshing the page
+// setup parameters (which will cause a re-generation of the PDF in
+// the renderer process - do we want a progress throbber shown?
+// Probably..), and packing up the PDF and job parameters and sending
+// them to the cloud.
+class CloudPrintFlowHandler : public DOMMessageHandler,
+ public NotificationObserver {
+ public:
+ explicit CloudPrintFlowHandler(const FilePath& path_to_pdf)
+ : path_to_pdf_(path_to_pdf) {}
+ virtual ~CloudPrintFlowHandler() {
+ // This will also cancel any task in flight.
+ CancelAnyRunningTask();
+ }
+
+ // DOMMessageHandler implementation.
+ virtual void RegisterMessages();
+
+ // NotificationObserver implementation.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // Callbacks from the page.
+ void HandleShowDebugger(const Value* value);
+ void HandleSendPrintData(const Value* value);
+ void HandleSetPageParameters(const Value* value);
+
+ // Call to get the debugger loaded on our hosted dialog page
+ // specifically. Since we're not in an official browser tab, only
+ // way to get the debugger going.
+ void ShowDebugger();
+
+ void SetDialogDelegate(CloudPrintHtmlDialogDelegate *delegate);
+ void CancelAnyRunningTask();
+
+ private:
+ // For unit testing.
+ virtual scoped_refptr<CloudPrintDataSender> CreateCloudPrintDataSender();
+
+ CloudPrintHtmlDialogDelegate* dialog_delegate_;
+ NotificationRegistrar registrar_;
+ FilePath path_to_pdf_;
+ scoped_refptr<CloudPrintDataSender> print_data_sender_;
+ scoped_ptr<CloudPrintDataSenderHelper> print_data_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(CloudPrintFlowHandler);
+};
+
+// State machine used to run the printing dialog. This class is used
+// to open and run the html dialog and deletes itself when the dialog
+// is closed.
+class CloudPrintHtmlDialogDelegate : public HtmlDialogUIDelegate {
+ public:
+ CloudPrintHtmlDialogDelegate(const FilePath& path_to_pdf,
+ int width, int height,
+ const std::string& json_arguments);
+ ~CloudPrintHtmlDialogDelegate();
+
+ // HTMLDialogUIDelegate implementation:
+ virtual bool IsDialogModal() const;
+ virtual std::wstring GetDialogTitle() const;
+ virtual GURL GetDialogContentURL() const;
+ virtual void GetDOMMessageHandlers(
+ std::vector<DOMMessageHandler*>* handlers) const;
+ virtual void GetDialogSize(gfx::Size* size) const;
+ virtual std::string GetDialogArgs() const;
+ virtual void OnDialogClosed(const std::string& json_retval);
+ virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { }
+
+ private:
+ CloudPrintHtmlDialogDelegate(CloudPrintFlowHandler* flow_handler,
+ int width, int height,
+ const std::string& json_arguments);
+ void Init(int width, int height, const std::string& json_arguments);
+
+ CloudPrintFlowHandler* flow_handler_;
+ mutable bool owns_flow_handler_;
+
+ // The parameters needed to display a modal HTML dialog.
+ HtmlDialogUI::HtmlDialogParams params_;
+
+ DISALLOW_COPY_AND_ASSIGN(CloudPrintHtmlDialogDelegate);
+};
+
+} // namespace internal_cloud_print_helpers
+
+#endif // CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_INTERNAL_H_
diff --git a/chrome/browser/printing/print_dialog_cloud_uitest.cc b/chrome/browser/printing/print_dialog_cloud_uitest.cc
new file mode 100644
index 0000000..753f5ae
--- /dev/null
+++ b/chrome/browser/printing/print_dialog_cloud_uitest.cc
@@ -0,0 +1,176 @@
+// Copyright (c) 2010 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.
+
+#include "chrome/browser/printing/print_dialog_cloud.h"
+#include "chrome/browser/printing/print_dialog_cloud_internal.h"
+
+#include <functional>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/singleton.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/dom_ui/chrome_url_data_manager.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/in_process_browser_test.h"
+#include "chrome/test/ui_test_utils.h"
+#include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_test_job.h"
+
+namespace {
+
+class TestData {
+ public:
+ TestData() {}
+
+ char* GetTestData() {
+ if (test_data_.empty()) {
+ FilePath test_data_directory;
+ PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
+ FilePath test_file =
+ test_data_directory.AppendASCII("printing/cloud_print_uitest.html");
+ file_util::ReadFileToString(test_file, &test_data_);
+ }
+ return &test_data_[0];
+ }
+ private:
+ std::string test_data_;
+};
+
+// A simple test URLRequestJob. We don't care what it does, only that
+// whether it starts and finishes.
+class SimpleTestJob : public URLRequestTestJob {
+ public:
+ explicit SimpleTestJob(URLRequest* request)
+ : URLRequestTestJob(request, test_headers(),
+ Singleton<TestData>()->GetTestData(), true) {}
+ private:
+ ~SimpleTestJob() {}
+};
+
+class TestResult {
+ public:
+ TestResult() : result_(false) {}
+ void SetResult(bool value) {
+ result_ = value;
+ }
+ bool GetResult() {
+ return result_;
+ }
+ private:
+ bool result_;
+};
+
+} // namespace
+
+class PrintDialogCloudTest : public InProcessBrowserTest,
+ public NotificationObserver {
+ public:
+ PrintDialogCloudTest() : handler_added_(false) {
+ PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory_);
+ GURL cloud_print_url(internal_cloud_print_helpers::kCloudPrintDialogUrl);
+ host_name_ = cloud_print_url.host();
+ }
+
+ // Must be static for handing into AddHostnameHandler.
+ static URLRequest::ProtocolFactory Factory;
+
+ virtual void SetUp() {
+ Singleton<TestResult>()->SetResult(false);
+ InProcessBrowserTest::SetUp();
+ }
+
+ virtual void TearDown() {
+ if (handler_added_) {
+ URLRequestFilter* filter = URLRequestFilter::GetInstance();
+ filter->RemoveHostnameHandler("http", host_name_);
+ handler_added_ = false;
+ }
+ InProcessBrowserTest::TearDown();
+ }
+
+ // Normally this is something I would expect could go into SetUp(),
+ // but there seems to be some timing or ordering related issue with
+ // the test harness that made that flaky. Calling this from the
+ // individual test functions seems to fix that.
+ void AddTestHandlers() {
+ if (!handler_added_) {
+ URLRequestFilter* filter = URLRequestFilter::GetInstance();
+ filter->AddHostnameHandler("http", host_name_,
+ &PrintDialogCloudTest::Factory);
+ handler_added_ = true;
+
+ registrar_.Add(this, NotificationType::LOAD_STOP,
+ NotificationService::AllSources());
+ }
+ }
+
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::LOAD_STOP) {
+ MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ registrar_.Remove(this, NotificationType::LOAD_STOP,
+ NotificationService::AllSources());
+ }
+ }
+
+ bool handler_added_;
+ std::string host_name_;
+ std::string test_data_;
+ FilePath test_data_directory_;
+ NotificationRegistrar registrar_;
+};
+
+URLRequestJob* PrintDialogCloudTest::Factory(URLRequest* request,
+ const std::string& scheme) {
+ if (request && (request->url() ==
+ GURL(internal_cloud_print_helpers::kCloudPrintDialogUrl)))
+ Singleton<TestResult>()->SetResult(true);
+ return new SimpleTestJob(request);
+}
+
+IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, HandlersRegistered) {
+ BrowserList::SetLastActive(browser());
+ ASSERT_TRUE(BrowserList::GetLastActive());
+
+ AddTestHandlers();
+
+ FilePath pdf_file =
+ test_data_directory_.AppendASCII("printing/cloud_print_uitest.pdf");
+ new PrintDialogCloud(pdf_file);
+
+ ui_test_utils::RunMessageLoop();
+
+ ASSERT_TRUE(Singleton<TestResult>()->GetResult());
+}
+
+#if defined(OS_CHROMEOS)
+// Disabled until the extern URL is live so that the Print menu item
+// can be enabled for Chromium OS.
+IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, DISABLED_DialogGrabbed) {
+ BrowserList::SetLastActive(browser());
+ ASSERT_TRUE(BrowserList::GetLastActive());
+
+ AddTestHandlers();
+
+ // This goes back one step further for the Chrome OS case, to making
+ // sure 'window.print()' gets to the right place.
+ ASSERT_TRUE(browser()->GetSelectedTabContents());
+ ASSERT_TRUE(browser()->GetSelectedTabContents()->render_view_host());
+
+ std::wstring window_print(L"window.print()");
+ browser()->GetSelectedTabContents()->render_view_host()->
+ ExecuteJavascriptInWebFrame(std::wstring(), window_print);
+
+ ui_test_utils::RunMessageLoop();
+
+ ASSERT_TRUE(Singleton<TestResult>()->GetResult());
+}
+#endif
diff --git a/chrome/browser/printing/print_dialog_cloud_unittest.cc b/chrome/browser/printing/print_dialog_cloud_unittest.cc
new file mode 100644
index 0000000..85a3ce3
--- /dev/null
+++ b/chrome/browser/printing/print_dialog_cloud_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2010 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.
+
+#include "chrome/browser/printing/print_dialog_cloud.h"
+#include "chrome/browser/printing/print_dialog_cloud_internal.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/notification_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::AtLeast;
+using testing::Return;
+using testing::_;
+using testing::A;
+
+static const char* const kPDFTestFile = "printing/cloud_print_unittest.pdf";
+static const char* const kEmptyPDFTestFile =
+ "printing/cloud_print_emptytest.pdf";
+
+FilePath GetTestDataFileName() {
+ FilePath test_data_directory;
+ PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
+ FilePath test_file = test_data_directory.AppendASCII(kPDFTestFile);
+ return test_file;
+}
+
+FilePath GetEmptyDataFileName() {
+ FilePath test_data_directory;
+ PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
+ FilePath test_file = test_data_directory.AppendASCII(kEmptyPDFTestFile);
+ return test_file;
+}
+
+char* GetTestData() {
+ static std::string sTestFileData;
+ if (sTestFileData.empty()) {
+ FilePath test_file = GetTestDataFileName();
+ file_util::ReadFileToString(test_file, &sTestFileData);
+ }
+ return &sTestFileData[0];
+}
+
+namespace internal_cloud_print_helpers {
+
+class MockCloudPrintFlowHandler : public CloudPrintFlowHandler {
+ public:
+ MOCK_METHOD0(RegisterMessages,
+ void());
+ MOCK_METHOD3(Observe,
+ void(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details));
+ MOCK_METHOD0(CreateCloudPrintDataSender,
+ scoped_refptr<CloudPrintDataSender>());
+};
+
+class MockCloudPrintHtmlDialogDelegate : public CloudPrintHtmlDialogDelegate {
+ public:
+ MOCK_CONST_METHOD0(IsDialogModal,
+ bool());
+ MOCK_CONST_METHOD0(GetDialogTitle,
+ std::wstring());
+ MOCK_CONST_METHOD0(GetDialogContentURL,
+ GURL());
+ MOCK_CONST_METHOD1(GetDOMMessageHandlers,
+ void(std::vector<DOMMessageHandler*>* handlers));
+ MOCK_CONST_METHOD1(GetDialogSize,
+ void(gfx::Size* size));
+ MOCK_CONST_METHOD0(GetDialogArgs,
+ std::string());
+ MOCK_METHOD1(OnDialogClosed,
+ void(const std::string& json_retval));
+ MOCK_METHOD2(OnCloseContents,
+ void(TabContents* source, bool *out_close_dialog));
+};
+
+} // namespace internal_cloud_print_helpers
+
+using internal_cloud_print_helpers::CloudPrintDataSenderHelper;
+using internal_cloud_print_helpers::CloudPrintDataSender;
+
+class MockExternalHtmlDialogUI : public ExternalHtmlDialogUI {
+ public:
+ MOCK_METHOD1(RenderViewCreated,
+ void(RenderViewHost* render_view_host));
+};
+
+class MockCloudPrintDataSenderHelper : public CloudPrintDataSenderHelper {
+ public:
+ // TODO(scottbyer): At some point this probably wants to use a
+ // MockTabContents instead of NULL, and to pre-load it with a bunch
+ // of expects/results.
+ MockCloudPrintDataSenderHelper() : CloudPrintDataSenderHelper(NULL) {}
+ MOCK_METHOD1(CallJavascriptFunction, void(const std::wstring&));
+ MOCK_METHOD2(CallJavascriptFunction, void(const std::wstring&,
+ const Value& arg1));
+ MOCK_METHOD3(CallJavascriptFunction, void(const std::wstring&,
+ const Value& arg1,
+ const Value& arg2));
+};
+
+// Testing for CloudPrintDataSender needs a mock DOMUI.
+class CloudPrintDataSenderTest : public testing::Test {
+ public:
+ CloudPrintDataSenderTest()
+ : file_thread_(ChromeThread::FILE, &message_loop_),
+ io_thread_(ChromeThread::IO, &message_loop_) {}
+
+ protected:
+ virtual void SetUp() {
+ mock_helper_.reset(new MockCloudPrintDataSenderHelper);
+ print_data_sender_ =
+ new CloudPrintDataSender(mock_helper_.get());
+ }
+
+ scoped_refptr<CloudPrintDataSender> print_data_sender_;
+ scoped_ptr<MockCloudPrintDataSenderHelper> mock_helper_;
+
+ MessageLoop message_loop_;
+ ChromeThread file_thread_;
+ ChromeThread io_thread_;
+};
+
+// TODO(scottbyer): DISABLED until the binary test file can get
+// checked in separate from the patch.
+TEST_F(CloudPrintDataSenderTest, DISABLED_CanSend) {
+ EXPECT_CALL(*mock_helper_, CallJavascriptFunction(_, _, _)).
+ WillOnce(Return());
+
+ FilePath test_data_file_name = GetTestDataFileName();
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(
+ print_data_sender_.get(),
+ &CloudPrintDataSender::ReadPrintDataFile,
+ test_data_file_name));
+ MessageLoop::current()->RunAllPending();
+}
+
+TEST_F(CloudPrintDataSenderTest, BadFile) {
+ EXPECT_CALL(*mock_helper_, CallJavascriptFunction(_, _, _)).Times(0);
+
+#if defined(OS_WIN)
+ FilePath bad_data_file_name(L"/some/file/that/isnot/there");
+#else
+ FilePath bad_data_file_name("/some/file/that/isnot/there");
+#endif
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(
+ print_data_sender_.get(),
+ &CloudPrintDataSender::ReadPrintDataFile,
+ bad_data_file_name));
+ MessageLoop::current()->RunAllPending();
+}
+
+TEST_F(CloudPrintDataSenderTest, EmptyFile) {
+ EXPECT_CALL(*mock_helper_, CallJavascriptFunction(_, _, _)).Times(0);
+
+ FilePath empty_data_file_name = GetEmptyDataFileName();
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(
+ print_data_sender_.get(),
+ &CloudPrintDataSender::ReadPrintDataFile,
+ empty_data_file_name));
+ MessageLoop::current()->RunAllPending();
+}
+
+// Testing for CloudPrintFlowHandler needs a mock
+// CloudPrintHtmlDialogDelegate, mock CloudPrintDataSender, and a mock
+// DOMUI.
+
+// Testing for CloudPrintHtmlDialogDelegate needs a mock
+// CloudPrintFlowHandler.
+
+// Testing for ExternalHtmlDialogUI needs a mock TabContents, mock
+// CloudPrintHtmlDialogDelegate (provided through the mock
+// tab_contents)
+
+// Testing for PrintDialogCloud needs a mock Browser.